Search code examples
javascriptpromisebluebird

Implementing a mix of Promise.all and Promise.settle


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.


Solution

  • 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;});
        });
    }