5

I need to implement a version of Promise.all that would take an array of promises and return the result as it usually does, plus also settles all promises, much like Promise.settle does it within the Bluebird library, except I cannot use Bluebird, and have to rely just on the standard promise protocol.

Would that be terribly complicated to implement? Or is it too much to ask here for an idea of how to implement it? I really hope not, so I'm asking, if anyone perhaps implemented it before, to share the idea of how to do it right.

The premise for this is to be able to use it within a database transaction that needs to do commit/rollback once the call has finished, and it cannot have loose promises still trying to resolve outside the transaction call.

EDIT: The link provided to another question is very useful, but it is not a complete answer to the question that was asked. A generic settle is a great example that helped a lot, but it needed to be simplified and wrapped into all logic to fit the transactions scenario described earlier.

vitaly-t
  • 24,279
  • 15
  • 116
  • 138
  • 3
    Code provided for exactly what you're asking for here (in the second code example named `promiseSettle()`): [Wait for multiple promises to be rejected](http://stackoverflow.com/questions/32241391/wait-for-multiple-promises-to-be-rejected). Marked this question as a dup of that one. – jfriend00 Sep 08 '15 at 22:52
  • It was a very useful reference, although offering only half the answer. – vitaly-t Sep 09 '15 at 00:27
  • What half is missing. It runs all promises and tells you when they are all done. – jfriend00 Sep 09 '15 at 00:31
  • It creates a needless array of objects with fulfilled statuses, and it doesn't implement the merging logic like the one I wrote based on the example. – vitaly-t Sep 09 '15 at 00:32
  • A simplification, more like, because some things in the generic implementation are not needed when it is done strictly in the context of `all` as the containing operation. – vitaly-t Sep 09 '15 at 00:37
  • It's a specialization of a generic capability that loses the ability to do any other flavors of `.settle()`. From a software design capability, I'd prefer to keep the generic function and then wrap it to offer the specialized result. That provides a lot more flexibility in the future at the cost of only a few more lines of code. – jfriend00 Sep 09 '15 at 01:23
  • Unmarked as a dup because the OP wasn't actually asking for `.settle()`, but some specialized derivation of that. – jfriend00 Sep 09 '15 at 01:25

3 Answers3

6

I think the solution by jfriend is overly complex because it builds on top of settle, it runs a semaphore and does a lot of weird stuff instead of using the built in primitives like .all.

Instead, if we build on Bluebird's newer reflect primitive (implementing it in native promises) we can get a much cleaner API and implementation:

function reflect(promise){
    return promise.then(x => ({state: "fulfilled", value: x}), // arrows, assume nodejs
                        e => ({state: "rejected" , value: e}));
}

On top of reflect, we can build the other primitives easily:

function settle(promises){
    return Promise.all(promises.map(reflect)); // much cleaner
}

If we want to wait and then resolve/reject based on values it's simply:

function allWait(promises){
    return settle(promises).then(results => {
       var firstFailed = results.find(r => r.state === "rejected");
       if(firstFailed) throw firstFailed.value;
       return results; 
    });
}
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • Wouldn't you rather reject with an array here? – Bergi Sep 09 '15 at 16:26
  • @Bergi I wanted to show the correct way to write the same code. Personally I would not handle transactions like this to begin with - I'd use disposers and the disposer pattern, like ORMs do. – Benjamin Gruenbaum Sep 09 '15 at 16:27
  • The question clearly states, and it is mentioned in comments in several places, that I needed a solution based on the standard promises, not one specific promise library. I cannot use Bluebird's `settle` or anything something like this. jfriend00's solution is the one that works for me. – vitaly-t Sep 09 '15 at 17:46
  • 2
    @vitaly-t this answer does not use bluebird promises or any promise library, everything here is standard. I have read your question. – Benjamin Gruenbaum Sep 09 '15 at 17:46
  • 1
    @vitaly-t I don't see bluebird (or anything bluebird specific) in his answer. Also, the solution here is shorter, more expressive and much more readable than the accepted answer. – Madara's Ghost Sep 09 '15 at 17:56
  • What does "=>" even mean? I'm not sure how to read such code. – vitaly-t Sep 09 '15 at 22:00
  • 1
    @vitaly-t open your developer tools (F12) go to the console and test it out, it's an ES2015 [arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) :) It's definitely JavaScirpt and it works in modern browsers (Chrome, Firefox, Safari, Edge) today, more importantly - since your code is node then you can use it in your code no issues. If you don't want to use it - you can just change `x => y` to `function(x){ return y; }` in this case. – Benjamin Gruenbaum Sep 09 '15 at 22:02
  • Right, I'm using the very latest version of Node JS 0.12.7, and it doesn't recognize it. Also, in the code that I was using eventually I am resolving with an array of resolved results, and only for the reject i'm returning array of {success, result}. This makes it compatible with results from `promise.all`, and much easier to use ;) – vitaly-t Sep 09 '15 at 22:03
  • @vitaly-t that's not the latest version of Node by a long shot https://nodejs.org/en/blog/release/v4.0.0/ but if you feel like not using it - then you're welcome to use `function(x){ return y; }`. Arrow functions are a native ES2015 feature (very much like promises) - so I assumed since the topic is an ES2015 feature that's supported in modern environments (promises) then these features are acceptable to use here. Node 4.0 also has the advantage of having the unhandledRejection hooks so it can detect exceptions native promises swallowed - so it's well worth your upgrade. – Benjamin Gruenbaum Sep 09 '15 at 22:05
  • My question was initially around an implementation to be used inside transactions, for databases, so client-side is of no interest ;) – vitaly-t Sep 09 '15 at 22:07
  • @vitaly-t awesome, then you can use arrow functions safely :) A lot of times when you target client side you'd have to use tools that [turn new JavaScript to old JavaScript](http://babeljs.io/) for engines like internet explorer. Since you're using Node then you can use modern JavaScript (classes, generators, arrow functions, let/const) out of the box. Anyway, this answer was more about correct (native) promise usage than anything else. – Benjamin Gruenbaum Sep 09 '15 at 22:08
  • I have updated my own solution - just to show what I was using in the end. And thank you for your input also, this gives some nice ideas as well. It's just that I did a few extra things on top of it, as I described in my code ;) – vitaly-t Sep 09 '15 at 22:15
3

Building on the generic promiseSettle() function from the other question, you could do this and have both a generic settle() type function and your much more specific version as a wrapper around it. This would give you the generic ability to do lots of .settle() types of behavior and have your own specific flavor and also build other specific flavors as needed:

So, here's the generic promiseSettle() that returns you the state of all the promises and resolves only when all the passed-in promises are done:

function promiseSettle(promises) {
    return new Promise(function(resolve) {
        var remaining = promises.length;
        // place to store results in original order
        var results = new Array(remaining);

        function checkDone() {
            if (--remaining === 0) {
                resolve(results);
            }
        }

        promises.forEach(function(item, index) {
            // check if the array entry is actually a thenable
            if (typeof item.then === "function") {
                item.then(function(value) {
                    // success
                    results[index] = {state: "fulfilled", value: value};
                    checkDone();
                }, function(err) {
                    // reject error
                    results[index] = {state: "rejected", value: err};
                    checkDone();
                });
            } else {
                // not a thenable, just return the item itself
                results[index] = {state: "fulfilled", value: item}
                --remaining;
            }
        });
        // special case for zero promises passed
        if (remaining === 0) {
            resolve(results);
        }
    });
}

And, here's a wrapper on it that gives you your specific behavior:

// Either fulfills with an array of results or
// rejects with the first error, but it does not do either
// until all promises have completed which makes it different than
// promise.all()
function promiseSettleAll(promises) {
    return promiseSettle(promises).then(function(results) {
        for (var i = 0; i < results.length; i++) {
            if (results[i].state !== "fulfilled") {
                // reject with the first error found
                throw results[i].value;
            }
        }
        // all were successful, return just array of values
        return results.map(function(item) {return item.value;});
    });
}
Community
  • 1
  • 1
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Simplified `promiseSettleAll()` by using a plain `for` loop. – jfriend00 Sep 09 '15 at 02:58
  • After a careful consideration, I decided to track all rejects also. It does seem like a good idea after all :) – vitaly-t Sep 09 '15 at 05:14
  • @vitaly-t - Interesting. What led to the change of mind? – jfriend00 Sep 09 '15 at 05:27
  • Use of the same method outside of transactions, in tasks. A database task is like a transaction, just without `begin/commit/rollback` parts, and in those cases one is likely to want details on every reject, since a failed task doesn't result in a `rollback`. I wasn't considering this case initially. – vitaly-t Sep 09 '15 at 06:10
  • I've first updated my example, but it got too much specifics in it now, so then I marked it for deletion, to avoid confusion, and accepted your answer, even though my final version was a bit different. Thank you! – vitaly-t Sep 09 '15 at 06:18
0

In the end of all the research, writing, testing and optimizations, it was turned into a library (spex) that focuses on this type of things.

Specifically, method batch is the one that implements the fusion of the logic described.

I am not republishing its source code here, because it now does a lot more than what was originally sought within the question.

vitaly-t
  • 24,279
  • 15
  • 116
  • 138
  • What is the goal of this code because it is not clear what you're trying to do here and apparently I did not understand what you were trying to do in your question either. In my code, you could just run `promiseSettle()` and then enumerate the returned results to see what succeeded and what failed. That is, after all, what Bluebird's `.settle()` does which is what I thought you asked for. – jfriend00 Sep 09 '15 at 00:33
  • First is to settle all the promises in the array, while doing it in such a way that's necessary in the context of the `all` operation that contains it, and then return the result with the same logic as `all` does. – vitaly-t Sep 09 '15 at 00:35
  • But, what do you do when a promise rejects. How do you communicate that back. You can't return results the same way `Promise.all()` does because you have to communicate back which ones succeeded and which ones failed. – jfriend00 Sep 09 '15 at 00:37
  • The code above does exactly all that. It's not that much different from the implementation suggested earlier, just simplified. P.S. You do not need to communicate which ones failed, only the first one that failed, according to `all` logic. – vitaly-t Sep 09 '15 at 00:38
  • So, you only return two states: 1) all succeeded with results in an array or 2) something failed and here's the first error that occured. Is that right? So, if anything fails, you have access to no results and no idea which request failed? – jfriend00 Sep 09 '15 at 00:42
  • That is correct. That's exactly what I was looking for, as per my original question, because this is the kind of logic a database transaction needs when executing an array of promise-queries, to figure out whether it should do a `rollback` or `commit`. And again yes, I don't care about results, should any promise reject, I just need the first failure details, and that's it. – vitaly-t Sep 09 '15 at 00:43
  • OK, I've posted an answer that is a wrapper on the generic `.settle()` that achieves your specific desires. This, obviously gives you access to the generic `.settle()` capabilities and the ability to build other specialized wrappers too. Apparently this isn't your first choice for implementation, but it is how I would do it to preserve access to the generic capability and other derived behaviors. There is little loss of anything by having a few more lines of code and much flexibility retained by having the generic version that can actually return all results even when something rejects. – jfriend00 Sep 09 '15 at 01:19