Promise.all, allSettled, race and any: Which One to Use
By Nitheesh Rajendran · · 4 min read
JavaScript has four built-in ways to wait on several promises at once: Promise.all, Promise.allSettled, Promise.race and Promise.any. They differ in two things: when they settle and what happens when one input fails. Picking the wrong one is a common source of bugs, such as a dashboard that goes blank because one widget's request failed.
This article runs all four against the same inputs so you can see the differences directly. Everything below runs in Node.js 15 or newer (Promise.any was added in ES2021) and in current browsers.
A helper for the examples
The examples use a small helper that resolves or rejects after a delay:
const wait = (ms, value, fail) =>
new Promise((resolve, reject) =>
setTimeout(() => (fail ? reject(new Error(value)) : resolve(value)), ms)
);
Promise.all: all must succeed
Promise.all takes an iterable of promises and returns a promise for an array of results, in the same order as the inputs. If any input rejects, the combined promise rejects immediately with that error.
try {
await Promise.all([wait(30, "a"), wait(10, "boom", true)]);
} catch (e) {
console.log("Promise.all rejected:", e.message);
}
// Promise.all rejected: boom
Use it when the results only make sense together: loading a page that needs the user, their permissions and their settings, for example. If one piece is missing there is nothing useful to show.
Two details matter in practice:
- The other promises are not cancelled. They keep running;
Promise.alljust stops waiting for them. - Only the first rejection is reported. Later failures are silently ignored.
Promise.allSettled: wait for everything, never reject
Promise.allSettled waits until every input has either fulfilled or rejected, then returns an array of result objects. It never rejects because of its inputs.
const results = await Promise.allSettled([wait(30, "a"), wait(10, "boom", true)]);
console.log(results.map((r) => (r.status === "fulfilled" ? r.value : r.reason.message)));
// [ 'a', 'boom' ]
Each entry is either { status: "fulfilled", value } or { status: "rejected", reason }. Use it for independent jobs where partial success is fine: sending notifications, warming caches, loading dashboard widgets. Check each entry and handle failures individually.
One catch: if you JSON.stringify a rejected entry, the Error object serialises as {} because its properties are not enumerable. Read reason.message instead.
Promise.race: first to settle wins
Promise.race settles as soon as the first input settles, whether it fulfilled or rejected.
console.log(await Promise.race([wait(30, "slow"), wait(10, "fast")]));
// fast
The classic use is a timeout:
const timeout = (ms) =>
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), ms));
const data = await Promise.race([fetch("/api/report"), timeout(5000)]);
Remember that the losing promise keeps running. For fetch, pair this with an AbortController (or use AbortSignal.timeout(5000)) if you also want to cancel the request.
Promise.any: first success wins
Promise.any is like race but ignores rejections until all inputs have rejected. It fulfils with the first successful result.
console.log(await Promise.any([wait(10, "down", true), wait(30, "mirror-2")]));
// mirror-2
If every input rejects, it rejects with an AggregateError whose errors array holds each reason:
try {
await Promise.any([wait(5, "x", true), wait(6, "y", true)]);
} catch (e) {
console.log(e.constructor.name, e.errors.map((x) => x.message));
}
// AggregateError [ 'x', 'y' ]
It suits redundancy: request the same resource from several mirrors and take whichever answers first.
Side-by-side summary
| Method | Fulfils when | Rejects when | Result |
|---|---|---|---|
Promise.all |
all fulfil | any rejects (first one) | array of values |
Promise.allSettled |
all settle | never | array of status objects |
Promise.race |
first settles with success | first settles with failure | one value or error |
Promise.any |
first fulfils | all reject | one value or AggregateError |
A quick way to choose
- Do you need every result, and is a single failure fatal? Use
all. - Do you need every outcome, success or failure? Use
allSettled. - Do you only care about the fastest answer, success or not (timeouts)? Use
race. - Do you want the fastest successful answer? Use
any.
Common mistakes
- Awaiting in a loop.
for (const url of urls) await fetch(url)runs requests one after another. Build the array of promises first, then pass it to a combinator. - Forgetting that work continues. None of these cancel the other promises. Use
AbortControllerwhen you need real cancellation. - Unhandled rejections after
allfails. The first rejection is reported, but later ones are handled by the combinator, so they will not crash your process. A rejected promise that you never pass to a combinator, however, will. - Assuming order of completion.
allandallSettledreturn results in input order, not completion order.
Summary
The four combinators answer different questions: "all or nothing" (all), "tell me everything" (allSettled), "whoever is first" (race) and "first success" (any). Decide how a single failure should affect the result, and the right method follows.