Why does JavaScript's `Promise.all` not run all promises in failure conditions?
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.
Because Promise.all
guarantees they all succeeded. Simple as that.
It's the most useful building block along with Promise.race
. Everything else can be built on those.
There's no settle
, because it's so trivial to build like this:
Promise.all([a(), b(), c()].map(p => p.catch(e => e)))
There's no easy way to build Promise.all
on top of settle
, which may be why it's not the default. settle
would also have had to standardize a way to distinguish success values from errors, which may be subjective and depend on the situation.
Update: There's now a Promise.allSettled that does exactly this.