TechEarl

Promise.all vs allSettled vs race vs any in JavaScript

Which JavaScript promise combinator to reach for: Promise.all (fail-fast), allSettled (every result, never rejects), race (first to settle), any (first fulfilled). With the gotchas that bite people.

Ishan Karunaratne⏱️ 9 min readUpdated
Share thisCopied
When to use Promise.all, Promise.allSettled, Promise.race, and Promise.any in JavaScript: behavior, rejection rules, and result shapes compared.

JavaScript gives you four ways to combine an array of promises, and the right one depends entirely on how you want failure handled. The short version:

  • Promise.all waits for every promise, but rejects the moment any one rejects. All-or-nothing.
  • Promise.allSettled waits for every promise and never rejects; you get an array of outcomes, each marked fulfilled or rejected.
  • Promise.race settles as soon as the first promise settles, whether that is a fulfillment or a rejection.
  • Promise.any settles as soon as the first promise fulfills, ignoring rejections; it rejects only if all of them reject.

If you remember nothing else: use all when you need every result and want to bail on the first error, and allSettled when you need every result and a failure of one should not sink the others. race and any are for "first one wins" cases, and they differ on whether a rejection counts as winning.

At a glance

All four take an iterable of promises and return a single promise. They diverge on when they settle, what makes them reject, and the shape of what you get back.

CombinatorSettles whenRejects whenFulfillment value
Promise.allall promises fulfillthe first rejection (short-circuits)array of fulfillment values, in input order
Promise.allSettledevery promise has settledneverarray of {status, value} / {status, reason} objects
Promise.racethe first promise settlesfirst settle is a rejectionthe first settled value
Promise.anythe first promise fulfillsall reject (with AggregateError)the first fulfillment value

Note the symmetry: all and race reject on the first rejection; allSettled and any are the failure-tolerant pair (allSettled never rejects, any rejects only when there is nothing left to succeed).

Promise.all: all-or-nothing, fail fast

Reach for all when you genuinely need every result and a single failure means the whole operation is invalid. Three API calls that feed one render, for example: if any of them fails you cannot draw the page, so failing fast is correct.

javascript
const [user, orders, settings] = await Promise.all([
  teFetchJson("/api/user"),
  teFetchJson("/api/orders"),
  teFetchJson("/api/settings"),
]);

The destructuring works because all preserves input order regardless of which promise resolved first. The catch (literally) is the short-circuit: the instant one promise rejects, the returned promise rejects with that reason, and you lose the values from the others even if they already resolved.

javascript
try {
  const results = await Promise.all([ok1, willReject, ok2]);
} catch (err) {
  // err is willReject's reason. ok1 and ok2's values are gone.
}

The other promises are not cancelled (JavaScript has no cancellation here); they keep running, and their results are simply discarded. If you need the successful results even when one fails, you want allSettled.

Promise.allSettled: every outcome, never rejects

allSettled (Node 12.9, mid-2019) waits for all promises to settle and resolves to an array describing each one. It does not short-circuit and it does not reject. Each entry is either {status: "fulfilled", value} or {status: "rejected", reason}.

javascript
const results = await Promise.allSettled([ok1, willReject, ok2]);
// [
//   { status: "fulfilled", value: ... },
//   { status: "rejected",  reason: ... },
//   { status: "fulfilled", value: ... },
// ]

const succeeded = results.filter(r => r.status === "fulfilled").map(r => r.value);
const failed = results.filter(r => r.status === "rejected").map(r => r.reason);

This is the right tool when each task is independent: send five notifications, fan out to several mirrors, warm a batch of caches. One failure should not throw away the four that worked.

Gotcha: a trailing .catch on allSettled is dead code

Because allSettled never rejects, any .catch chained onto it is unreachable. I have reviewed plenty of code that does this and the author believes it is handling errors:

javascript
// The .catch NEVER fires. It is dead code.
Promise.allSettled(tasks)
  .then(handle)
  .catch(err => console.error("never reached", err));

There is no rejection to catch, so the handler is decoration. The errors are inside the resolved array, on the reason field of each {status: "rejected"} entry. Handle them there.

Antipattern this replaces: .map(p => p.catch(() => undefined))

Before allSettled existed, the common trick to stop Promise.all short-circuiting was to swallow each rejection so it looked like a fulfillment:

javascript
// Old hack: don't do this anymore.
const results = await Promise.all(
  tasks.map(p => p.catch(() => undefined)),
);

It works in the narrow sense, but it masks real errors: a genuine bug, a thrown TypeError, an unhandled exception all collapse into undefined, indistinguishable from a legitimately empty result. allSettled is the native replacement and it keeps the reason, so you can tell a deliberate "no data" apart from a thrown error. If you are still carrying the .catch(() => undefined) pattern, that is the one to retire.

Promise.race: first to settle, win or lose

race settles with the first promise to settle, full stop. People reach for it expecting "first one to succeed," and that is the trap: if the first promise to settle rejects, race rejects with that reason, even if every other promise would have fulfilled.

javascript
// Timeout pattern: whichever finishes first wins.
const data = await Promise.race([
  teFetchJson("/api/slow"),
  teTimeout(5000), // rejects after 5s
]);

That timeout idiom is the canonical legitimate use: race the real work against a promise that rejects on a timer. For a genuine network timeout, AbortSignal.timeout() is cleaner and actually cancels the request (see how to add a timeout to fetch with AbortController); but the racing pattern is still the textbook example of what race is for.

If you want "first one to succeed, and only fail if they all fail," that is not race, that is any.

Promise.any: first to fulfill, AggregateError if all fail

any (Node 15 / Chrome 85, V8 8.5, late 2020) is the optimistic combinator: it resolves with the first fulfillment and ignores rejections. It rejects only when every promise has rejected, and it rejects with an AggregateError whose .errors array holds all the individual reasons.

javascript
// Hit several mirrors; take whichever responds first.
try {
  const fastest = await Promise.any([
    teFetchJson("https://mirror-a.example/file"),
    teFetchJson("https://mirror-b.example/file"),
    teFetchJson("https://mirror-c.example/file"),
  ]);
} catch (err) {
  // Only reached if ALL mirrors failed.
  // err is an AggregateError; err.errors is the array of reasons.
  console.error("all mirrors down", err.errors);
}

This is the redundancy pattern: query several equivalent sources and use whichever wins. A single rejection is fine; only a total wipeout is an error, and the AggregateError hands you every reason so you can log what went wrong everywhere at once.

Picking one

  • Need every result, one failure invalidates the batch → Promise.all.
  • Need every result, failures are independent and worth keeping → Promise.allSettled.
  • Want the first thing to finish, and a fast failure should count as the result (timeouts) → Promise.race.
  • Want the first success from interchangeable sources, tolerating individual failures → Promise.any.

For the underlying mechanics of promises, chaining, and async/await, start with the complete guide to JavaScript promises. When you are running these over a large array, do not fan out thousands of requests at once, see limit concurrent promises in JavaScript. And for the failure modes that show up around all of this, common JavaScript promise mistakes covers the ones I see most.

Browser and runtime support

Promise.all and Promise.race are original ES2015 and supported everywhere you would run JavaScript today. Promise.allSettled landed in Node 12.9.0 (August 2019) and across evergreen browsers around the same time. Promise.any (and AggregateError) shipped with V8 8.5 in Chrome 85 and Node 15, both in late 2020. In 2026 all four are universally available in current browsers and any supported Node release, so you can use them without polyfills.

FAQ

See also

Sources

Authoritative references this article was fact-checked against.

TagsJavaScriptpromisesPromise.allPromise.allSettledPromise.racePromise.anyasyncNode.js

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

How to sleep(), wait, and poll in JavaScript

JavaScript has no blocking sleep(). Here is the one-liner that actually works (await a Promise around setTimeout), Node's native timers/promises, a cancellable polling helper, and the setTimeout 4ms-clamp and Date.now vs performance.now gotchas.