Promise.all, allSettled, race and any
The four ways to combine promises, what each one does with a rejection, and how to pick the right one without guessing.
Updated
Four combinators, two questions
All four take an iterable of promises and return one promise. They differ on exactly two questions: when does it settle, and what does a rejection do.
| Settles when | On rejection | |
|---|---|---|
Promise.all | all fulfil | rejects immediately with the first error |
Promise.allSettled | all settle | never rejects — reports it as a result |
Promise.race | the first one settles | rejects if the first to settle rejected |
Promise.any | the first one fulfils | rejects only if all reject, with an AggregateError |
Read the table once and most "which one do I use" questions answer themselves.
all — everything, or nothing
const [user, orders] = await Promise.all([
fetchUser(id),
fetchOrders(id),
]);Both requests start immediately and run concurrently. The result array is in input order, not completion order — this is the property people rely on without noticing.
The catch is the fail-fast behaviour. If fetchOrders rejects, you get that error and fetchUser's result is discarded — but its request is not cancelled. A rejected Promise.all does not stop the other work; it just stops you from seeing it.
allSettled — when a partial answer is still an answer
const results = await Promise.allSettled(urls.map(fetchOne));
const ok = results.filter((r) => r.status === 'fulfilled').map((r) => r.value);
const failed = results.filter((r) => r.status === 'rejected');Every entry is { status: 'fulfilled', value } or { status: 'rejected', reason }. The combined promise never rejects, so a try/catch around it catches nothing — the errors are data now, and you have to look at them.
Use it whenever one failure should not void the batch: sending notifications, warming caches, importing rows.
race — the first to settle, whatever it is
const result = await Promise.race([
fetchData(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('timeout')), 5000)
),
]);This is the timeout pattern, and it is what race is really for. Note the asymmetry: if fetchData rejects in 10 ms, race rejects in 10 ms — it does not wait for a happier outcome. That is the difference from any, and it is the reason a "first successful response" implementation built on race is a bug waiting for its first flaky endpoint.
any — the first success, ignoring failures
const fastest = await Promise.any([
fetch(mirrorA),
fetch(mirrorB),
fetch(mirrorC),
]);Rejections are tolerated until there are no promises left. If all reject you get an AggregateError, whose errors array holds every reason in input order.
Three details that cause real bugs
An empty array is not an edge case you can ignore. Promise.all([]) fulfils immediately with []. Promise.any([]) rejects immediately with an AggregateError. Promise.race([]) never settles at all — it hangs, silently, forever.
The promises start before the combinator does. .map(fetchOne) fires every request as the array is built. Promise.all only decides how to *wait*. If you need to limit concurrency, the combinator is the wrong tool — you need a queue.
Non-promise values are allowed. Anything that is not a thenable is wrapped with Promise.resolve, so Promise.all([1, fetchX()]) works and puts 1 in the first slot.
Rejection handlers must be attached *when the promise is created*, not later. Building an array of promises, awaiting something else, then passing them to
allSettledcan produce an unhandled rejection warning in between — the promise rejected while nobody was listening.
Now practice it
Reading this page is the cheap half. These are the exercises that make you use it.
- Challengemedium
Implement Promise.all
Input order, the empty array, and resolving only when the count is complete — all three in twenty lines.
- ChallengemediumPro
Implement Promise.race
Shows how little separates race from any: one branch on whether a rejection ends it.
- ChallengemediumPro
Implement Promise.allSettled
The one that never rejects, which means every failure path has to be turned into a result object.