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.allwaits for every promise, but rejects the moment any one rejects. All-or-nothing.Promise.allSettledwaits for every promise and never rejects; you get an array of outcomes, each marked fulfilled or rejected.Promise.racesettles as soon as the first promise settles, whether that is a fulfillment or a rejection.Promise.anysettles 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.
| Combinator | Settles when | Rejects when | Fulfillment value |
|---|---|---|---|
Promise.all | all promises fulfill | the first rejection (short-circuits) | array of fulfillment values, in input order |
Promise.allSettled | every promise has settled | never | array of {status, value} / {status, reason} objects |
Promise.race | the first promise settles | first settle is a rejection | the first settled value |
Promise.any | the first promise fulfills | all 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.
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.
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}.
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:
// 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:
// 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.
// 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.
// 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
- The complete guide to JavaScript promises: how promises, chaining, and
async/awaitactually work, the foundation for all four combinators. - Common JavaScript promise mistakes: the failure modes around promises, including swallowed errors and unhandled rejections.
- Limit concurrent promises in JavaScript: the promise-pool pattern for when
Promise.allover a huge array fans out too many requests at once. - Add a timeout to fetch with AbortController: the modern alternative to the
Promise.racetimeout idiom that actually cancels the request.
Sources
Authoritative references this article was fact-checked against.
- Promise.all() — MDNdeveloper.mozilla.org
- Promise.allSettled() — MDNdeveloper.mozilla.org
- Promise.race() — MDNdeveloper.mozilla.org
- Promise.any() — MDNdeveloper.mozilla.org
- AggregateError — MDNdeveloper.mozilla.org





