Promise.all vs Promise.allSettled in JavaScript
I was reading the MDN docs on JavaScript promises and thought the difference between Promise.all
and Promise.allSettled
was interesting and slightly nuanced, and would be fun to discuss.
About Promises
From the docs linked above:
A
Promise
is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action’s eventual success value or failure reason.
A Promise is in one of these states:
>pending
: initial state, neither fulfilled nor rejected.
>fulfilled
: meaning that the operation completed successfully.
>rejected
: meaning that the operation failed.
You can instantiate a promise like so:
const someFutureValue = new Promise((resolve, reject) => {
setTimeout(resolve, 2000);
});
Difference between all and allSettled
tldr; Promise.all
will immediately reject if any of the promises fail to resolve, wherease Promise.allSettled
will await the completion of all promises.
Promise.all()
Promise.all()
is passed an iterable (usually an array of other promises) and will attempt to resolve all of them. If any of these promises throws an exception or reject
s, Promise.all
will immediateley invoke its reject
.
You should always surround Promise.all()
with a try/catch
or a .catch()
.
const promiseFunction = async () => {
const resolvingPromise = new Promise((resolve) =>
setTimeout(() => resolve("success"), 1000)
);
const rejectingPromise = new Promise((resolve, reject) =>
setTimeout(() => reject("fail"), 500)
);
try {
const allPromises = await Promise.all([resolvingPromise, rejectingPromise]);
} catch (e) {
// e is 'fail', which is the contents of the reject function from rejectingPromise
// resolvingPromise = Promise {<pending>}
console.log(e);
}
};
promiseFunction();
In the above code snippet, when console.log(e)
is called, the status of resolvingPromise
will still be pending. The code invoked in the catch statement is run roughly 500ms
after the promiseFunction
invocation - since rejectingPromise
rejects after 500ms.
const promiseFunction = async () => {
const resolvingPromise = new Promise((resolve) =>
setTimeout(() => resolve("success"), 1000)
);
const resolvingPromiseTwo = new Promise((resolve, reject) =>
setTimeout(() => resolve("successTwo"), 500)
);
try {
const allPromises = await Promise.all([
resolvingPromise,
resolvingPromiseTwo,
]);
console.log(allPromises);
// allPromises = ['success', 'successTwo']
} catch (e) {
// this code block is never executed
console.log(e);
}
};
promiseFunction();
The Promise.all
resolves 1000ms after invocation - resolvingPromiseTwo
resolves after 500ms, and resolvingPromise
resolves after 1s, which completes all the promises, which allows the Promise.all
promise to resolve. Promise.all()
returns an array with the resolved value of each of the Promises passed to it, in the order in which they are passed. In the above case, it is an array of strings = ['success', 'successTwo']
.
Promise.allSettled()
Promise.allSettled()
is also passed an iterable (usually an array of other promises) and will attempt to resolve all of them. If any of these promises throws an exception or reject
s, its status is set to rejected
.
An important note is that Promise.allSettled
can never reject. You do not need to wrap it with try/catch
- it will always resolve.
const promiseFunction = async () => {
const resolvingPromise = new Promise((resolve) =>
setTimeout(() => resolve("success"), 1000)
);
const rejectingPromise = new Promise((resolve, reject) =>
setTimeout(() => reject("fail"), 500)
);
try {
const allPromises = await Promise.allSettled([
resolvingPromise,
rejectingPromise,
]);
console.log(allPromises);
// allPromises
// [
// {status: "fulfilled", value: 'success'},
// {status: "rejected", reason: 'fail'}
// ]
} catch (e) {
// this code block is never executed
console.log(e);
}
};
promiseFunction();
Promise.allSettled
resolves after 1s - it awaits all the promises, regardless of their status or if they reject
. It then returns an array of objects. It’s up to the caller to check if each promise fulfilled
or rejected
. If it fulfilled
, the value
of the object will be contain what was resolve
d. If the status is rejected
, it will contain a key called reason
, which is what was thrown or reject
ed.
Bonus: Promise.race
There is also another interest function in Promise
called Promise.race
.
The
Promise.race()
method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise. 1
const promiseFunction = async () => {
const resolvingPromise = new Promise((resolve) =>
setTimeout(() => resolve("success"), 1000)
);
const resolvingPromiseTwo = new Promise((resolve, reject) =>
setTimeout(() => resolve("successTwo"), 500)
);
try {
const racedPromises = await Promise.race([
resolvingPromise,
resolvingPromiseTwo,
]);
console.log(racedPromises);
// both promises would've resolved, but resolvingPromiseTwo was faster, so racedPromises = 'successTwo'`
} catch (e) {
// this code block is only executed if the first promise to settle rejects/throws
console.log(e);
}
};
promiseFunction();
In the above, console.log(racedPromises)
is called 500ms after invocation - both promises would’ve resolved, but resolvingPromiseTwo
was faster, and it is what Promise.race
resolves with.
This is pretty specific behavior - most of the time this is probably not what you want, but it’s pretty neat!
Footnotes
-
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race ↩
JonLuca at 12:50