Back to JavaScript
JavaScript
medium
very high
mid

When do you use Promise.all, allSettled, race, or any?

all = fail-fast aggregation. allSettled = collect every outcome. race = first to settle (resolve or reject). any = first to *resolve*, ignores rejections until all fail.

7 min read·~12 min to think through

Four combinators with very different semantics:

  • Promise.all([p1, p2]) — resolves with an array of values once all resolve. Rejects immediately with the first rejection. Other promises keep running but their results are dropped. Use when you need every result and a partial result is meaningless.
  • Promise.allSettled([p1, p2]) — always resolves, with an array of { status: 'fulfilled'|'rejected', value|reason }. Use when partial success is fine — dashboards, telemetry, fan-out where you want to render whatever returned.
  • Promise.race([p1, p2]) — settles with the first promise to settle, whether resolve or reject. Use for timeouts: race the work against setTimeout(reject).
  • Promise.any([p1, p2]) — resolves with the first fulfilled promise; only rejects (with AggregateError) if every input rejects. Use for redundancy: the same request to multiple mirrors.

Key gotcha: Promise.all does not cancel the losers. If you start 5 fetches and one rejects, the other 4 still run, still consume bandwidth, and their unhandled rejections can warn the console — wire them through AbortController if you need real cancellation.

Code

ts
const ok    = Promise.resolve(1);
const slow  = new Promise(r => setTimeout(() => r(2), 200));
const fail  = Promise.reject(new Error("nope"));

await Promise.all([ok, slow, fail]).catch(e => e.message);
// "nope"  — fails fast

await Promise.allSettled([ok, slow, fail]);
// [{fulfilled,1},{fulfilled,2},{rejected, Error: nope}]

await Promise.race([slow, fail]).catch(e => e.message);
// "nope"  — first to settle, and it rejected

await Promise.any([fail, slow]);  // 2  — first fulfilled
Side-by-side outcomes
ts
async function fetchWithTimeout(url: string, ms = 3000) {
  const ctrl = new AbortController();
  const timeout = new Promise<never>((_, rej) =>
    setTimeout(() => { ctrl.abort(); rej(new Error("timeout")); }, ms),
  );
  return Promise.race([fetch(url, { signal: ctrl.signal }), timeout]);
}
Timeout pattern with race + AbortController

Follow-up questions

  • How would you implement Promise.all from scratch?
  • Why doesn't Promise.all cancel the other promises on rejection?
  • When would you prefer allSettled over all in a UI dashboard?

Common mistakes

  • Using `Promise.all` for independent panels and crashing the whole UI on one failure.
  • Forgetting that `race` resolves on rejection too — and being surprised when an error wins.
  • Assuming losers in `all` are cancelled (they aren't).

Performance considerations

  • Concurrency control: `Promise.all(arr.map(fetch))` with a 1000-item array opens 1000 sockets. Use a pool/limit (`p-limit`) for bounded concurrency.

Edge cases

  • `Promise.all([])` resolves with `[]` immediately. `Promise.any([])` rejects with empty `AggregateError`.
  • `Promise.race([])` returns a forever-pending promise — easy way to leak.

Real-world examples

  • Server-side rendering: `Promise.all` for required data, `allSettled` for nice-to-have widgets so a slow widget can't block the page.

Senior engineer discussion

Senior signal: cancellation strategy with AbortController, structured concurrency patterns, and the difference between an unhandled rejection (process-level event) and a swallowed one inside `all`.