JavaScript · Async
`Promise.all/allSettled/race/any`
⚡ TL;DR — Four combinators over an iterable of promises:
all= every value or the first rejection (fail-fast);allSettled= wait for everything, never rejects;race= first to settle (fulfil or reject);any= first to fulfil, else anAggregateError. Picking the wrong one is a correctness bug, not a style choice.
🧠 Mental model
They differ on two axes: how many results you need, and whether a rejection ends it early.
| Combinator | Fulfils when | Rejects when | Result shape |
|---|---|---|---|
all |
all fulfil | any rejects (first) | array of values, in input order |
allSettled |
all settle | never | array of {status, value | reason} |
race |
first settles (either) | first settle is a rejection | that single value / reason |
any |
first fulfils | all reject | value / AggregateError of reasons |
The two "first-past-the-post" ones trip people up: race settles on the first to finish however it finishes — a fast rejection beats a slow success. any ignores rejections and waits for the first success. If you want a timeout, that's race; if you want "first server that answers OK," that's any.
⚙️ How it actually works
allis fail-fast: the first rejection rejects the aggregate immediately — but the other promises are not cancelled. Promises have no cancellation; the losers keep running, their side effects still land, their rejections may surface as unhandled. To actually stop them you needAbortController.allandallSettledpreserve input order in the result array, regardless of which settled first. The array index is fixed at iteration time, not completion time.allSettlednever rejects. You get one entry per input, each{status:'fulfilled', value}or{status:'rejected', reason}. You must inspect them — forgetting to filter for'rejected'silently hides failures.any(ES2021) collects rejections and only rejects once all have — with anAggregateErrorwhose.errorsarray holds each reason.- Empty iterables are the edge:
all([])andallSettled([])fulfil immediately with[];any([])rejects with anAggregateError;race([])stays pending forever.
💻 Code
// Timeout pattern — race real work against a rejecting timer
const withTimeout = (work, ms) =>
Promise.race([
work,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('timeout')), ms)),
]);
// Partial success — allSettled, then split winners from losers
const results = await Promise.allSettled(urls.map((u) => fetch(u)));
const ok = results.filter((r) => r.status === 'fulfilled').map((r) => r.value);
const failed = results.filter((r) => r.status === 'rejected').map((r) => r.reason);
// Polyfill of Promise.all — shows fail-fast + order preservation
Promise.myAll = (items) => new Promise((resolve, reject) => {
const arr = [...items];
const out = new Array(arr.length);
let remaining = arr.length;
if (remaining === 0) return resolve(out); // empty → resolve now
arr.forEach((item, i) => {
Promise.resolve(item).then( // wrap non-promise values
(value) => {
out[i] = value; // index fixes input order
if (--remaining === 0) resolve(out); // last one wins
},
reject, // FIRST rejection settles the aggregate
);
});
});
⚖️ Trade-offs
allfor "I need all of them, and any failure is fatal" — a dashboard that can't render with a missing panel.allSettledfor "show me what succeeded" — render the panels that loaded, mark the rest as errored. Choosingallwhen you wanted partial success turns one flaky request into a blank screen.raceis for deadlines,anyis for redundancy. Race the work against a timer;anyacross mirror endpoints and take the first healthy one.- None of them cancel the losers. If un-run work is expensive, pair the combinator with an
AbortControllerand abort the rest once you have your answer. - When NOT to fan out: if the requests hit the same rate-limited backend, launching 200 in parallel via
allcan get you throttled — batch with a concurrency limit instead.
💣 Gotchas interviewers probe
racesettles on the first rejection too. People expect "first success" and get burned when the fastest promise rejects. First-success isany, notrace.alldoesn't cancel siblings on rejection. The other requests still complete and can throw unhandled rejections. Naming this is a strong signal.allSettlednever rejects — sotry/catcharound it catches nothing. You must walk the results and checkstatusyourself.- Empty-iterable behaviour differs:
all([])/allSettled([])→[];any([])→ rejects;race([])→ hangs forever. Aracethat never settles is a real deadlock. - Order vs. completion order.
all's result array is in input order, not the order things resolved. Assuming completion order is a bug. - Non-promise values are allowed.
Promise.all([1, fetch(x)])works — plain values are wrapped viaPromise.resolve. That's why polyfills mustPromise.resolve(item). any's failure is anAggregateError, not a normalError. Readerr.errorsfor the individual reasons.
🎯 Say this in the interview
"I pick by two questions: do I need all the results or just one, and does a failure end it early.
allwaits for every value but is fail-fast — the first rejection rejects the whole thing, though importantly it doesn't cancel the others, they keep running.allSettledwaits for everything and never rejects, so it's my choice when I want partial success and to report which ones failed.racesettles on whichever finishes first including a rejection, so it's the timeout primitive;anyis the one that waits for the first success and only rejects, with anAggregateError, if they all fail. The traps I watch for:racefiring on a fast rejection,allSettledmakingtry/catchpointless because it never throws, andrace([])hanging forever. If cancellation actually matters, I wire anAbortControllerin, since promises don't cancel themselves."
🔗 Go deeper
- MDN — Promise.all() — fail-fast semantics and order preservation.
- MDN — Promise.allSettled() — the
{status, value/reason}result shape. - MDN — Promise.any() — first-fulfilment and
AggregateError. - javascript.info — Promise API — all four side by side, with worked examples.