Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does JavaScript's `Promise.all` not run all promises in failure conditions?

According to MDN:

If any of the passed in promises rejects, the all Promise immediately rejects with the value of the promise that rejected, discarding all the other promises whether or not they have resolved.

The ES6 spec seems to confirm this.

My question is: Why does Promise.all discard promises if any of them reject, since I would expect it to wait for "all" promises to settle, and what exactly does "discard" mean? (It's hard to tell what "discard" means for in-flight promises vs. promises that may not have run yet.)

I ask because I frequently run into situations where I have a list of promises and want to wait for them all to settle and get all rejections that may have occurred, which Promise.all doesn't cater to. Instead, I have to use a hack like this:

const promises = []; // Array of promises
const settle = promise => promise.then(result => ({ result }), reason => ({ reason }));
Promise.all(promises.map(settle))
  .then(/ * check "reason" property in each element for rejection */);
like image 519
segfault Avatar asked Feb 17 '17 17:02

segfault


People also ask

What happens when a Promise fails in Promise all?

It resolves once all promises in the array resolve, or reject as soon as one of them rejects. In other words, it either resolves with an array of all resolved values, or rejects with a single error. Some libraries have something called Promise.

Does Promise all run promises in parallel?

Promises cannot "be executed". They start their task when they are being created - they represent the results only - and you are executing everything in parallel even before passing them to Promise. all . Promises are executed at the moment of creation.

Why do promises get rejected?

A Promise rejection indicates that something went wrong while executing a Promise or an async function. Rejections can occur in several situations: throwing inside an async function or a Promise executor/then/catch/finally callback, when calling the reject callback of an executor , or when calling Promise.

Does Promise all have a limit?

Assuming we have the processing power and that our promises can run in parallel, there is a hard limit of just over 2 million promises.

What is the use of promise all in JavaScript?

JavaScript | Promise.all () Method. The Promise.all () method is actually a promise that takes an array of promises (an iterable) as an input. It returns a single Promise that resolves when all of the promises passed as an iterable, which have resolved or when the iterable contains no promises. In simple way, if any of the passed-in promises ...

What happens if a promise fails to get executed in JavaScript?

In other words, if any promise fails to get executed, then Promise.all () method will return an error and it will not take into the account whether other promises are successfully fulfilled or not. Example 2: Here Promise.all resolves after 2000 ms and the output is shows as an array. Here, Promise.all is the order of the maintained promises.

How does promise all work in Python?

The Promise.all () method returns a single Promise that resolves when all of the promises passed as an iterable have resolved or when the iterable contains no promises. It rejects with the reason of the first promise that rejects. There is no implied ordering in the execution of the array of Promises given.

Why does my promise reject () method fail?

That's how Promise.all is designed to work. If a single promise reject()'s, the entire method immediately fails. There are use cases where one might want to have the Promise.all allowing for promises to fail. To make this happen, simply don't use any reject() statements in your promise.


1 Answers

The asynchronous operations associated with the promises are all run. If one of those promises rejects, then Promise.all() simply does not wait for all of them to complete, it rejects when the first promise rejects. That is just how it was designed to work. If you need different logic (like you want to wait for all of them to be done, no matter whether they fulfill or reject), then you can't use just Promise.all().

Remember, a promise is not the async operation itself. A promise is just an object that keeps track of the state of the async operation. So, when you pass an array of promises to Promise.all(), all those async operations have already been started and are all in-flight already. They won't be stopped or cancelled.

Why does Promise.all discard promises if any of them reject, since I would expect it to wait for "all" promises to settle.

It works the way it does because that's how it was designed and that is a very common use case when you don't want your code to continue if there was any sort of error. If it happens to not be your use case, then you need to use some implementation of .settle() which has the behavior you want (which you seem to already know).

What I find the more interesting question is why is there not a .settle() option in the specification and standard implementation since it is also a fairly common use case. Fortunately, as you have found, it is not a lot of code to make your own. When I don't need the actual reject reason and just want some indicator value to be placed into the array, I often use this fairly simple to use version:

// settle all promises.  For rejeted promises, return a specific rejectVal that is
// distinguishable from your successful return values (often null or 0 or "" or {})
Promise.settleVal = function(rejectVal, promises) {
    return Promise.all(promises.map(function(p) {
        // make sure any values or foreign promises are wrapped in a promise
        return Promise.resolve(p).catch(function(err) {
            // instead of rejection, just return the rejectVal (often null or 0 or "" or {})
            return rejectVal;
        });
    }));
};

// sample usage:
Promise.settleVal(null, someArrayOfPromises).then(function(results) {
    results.forEach(function(r) {
        // log successful ones
        if (r !== null) {
           console.log(r);
        }
    });
});

what exactly does "discard" mean?

It just means that the promises are no longer tracked by Promise.all(). The async operations they are associated with keep right on doing whatever they were going to do. And, in fact if those promises have .then() handlers on them, they will be called just as they normally would. discard does seem like an unfortunate term to use here. Nothing happens other than Promise.all() stops paying attention to them.


FYI, if I want a more robust version of .settle() that keeps track of all results and reject reasons, then I use this:

// ES6 version of settle that returns an instanceof Error for promises that rejected
Promise.settle = function(promises) {
    return Promise.all(promises.map(function(p) {
        // make sure any values or foreign promises are wrapped in a promise
        return Promise.resolve(p).catch(function(err) {
            // make sure error is wrapped in Error object so we can reliably detect which promises rejected
            if (err instanceof Error) {
                return err;
            } else {
                var errObject = new Error();
                errObject.rejectErr = err;
                return errObject;
            }
        });
    }));
}

// usage
Promise.settle(someArrayOfPromises).then(function(results) {
    results.forEach(function(r) {
       if (r instanceof Error) {
           console.log("reject reason", r.rejectErr);
       } else {
           // fulfilled value
           console.log("fulfilled value:", r);
       }
    });
});

This resolves to an array of results. If a result is instanceof Error, then it was a rejected, otherwise it's a fulfilled value.

like image 86
jfriend00 Avatar answered Oct 13 '22 00:10

jfriend00