Promise.all, allSettled, any, and race Fail Differently
Choose a Promise combinator by its failure result, ordering guarantees, and whether you need explicit cancellation for unfinished work.
Choosing a Promise combinator chooses the result contract, not how the input operations run. Promise.all() rejects when the first input rejection is observed. Promise.allSettled() waits and reports every outcome. Promise.any() fulfills when one input fulfills, or rejects after every input rejects. Promise.race() mirrors the first settlement of either kind. None of them cancels unfinished work.
The examples require ECMAScript 2021 or newer because that edition added Promise.any() and AggregateError. Promise.allSettled() appeared in ECMAScript 2020. Run the examples in an ES module or a console that supports top-level await.
Pick the result shape you need
| Combinator | When its result fulfills | When its result rejects | Empty iterable |
|---|---|---|---|
Promise.all() |
Every input fulfills | One input rejects | Fulfills with [] |
Promise.allSettled() |
Every input settles | Setup or iteration fails | Fulfills with [] |
Promise.any() |
One input fulfills | Every input rejects | Rejects with an empty AggregateError |
Promise.race() |
The first input fulfills | The first input rejects | Remains pending |
All four methods accept an iterable, not only an array. They pass each item through the constructor’s resolve method, so plain values participate as already-fulfilled promises. Their key differences are how they react to settlement and what they preserve from the input order.
Promise.all fails fast but does not stop the inputs
Use Promise.all() when every result is required. The returned promise fulfills with values in input order, even when the inputs finish in another order. If an input rejects, the first rejection handler that settles the combined result wins. For pending native promises, that is usually the earliest rejection in time. If several inputs have already settled, their reactions are queued as the iterable is consumed, so input order can decide which reason wins.
The ECMAScript algorithm for Promise.all() attaches a fulfillment handler for each input and the shared reject function as its rejection handler. Rejecting that result promise early does not undo the work represented by the other inputs:
function task(label, milliseconds, shouldReject = false) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`${label} finished`);
if (shouldReject) reject(new Error(label));
else resolve(label);
}, milliseconds);
});
}
try {
await Promise.all([
task('slow success', 30),
task('fast failure', 5, true),
]);
} catch (error) {
console.log(`caught ${error.message}`);
}
await new Promise((resolve) => setTimeout(resolve, 40));
The catch runs after fast failure finished, then slow success finished still appears. A rejected Promise.all() therefore means the combined result is unavailable. It does not mean the remaining writes, requests, or timers stopped.
The combinator has installed rejection handlers on inputs it consumed, so a later rejection from one of those inputs is not automatically an unhandled rejection. Its error can still be invisible to your application because the combined promise has already rejected. If each error matters, collect every outcome instead.
Promise.allSettled reports every outcome
Use Promise.allSettled() for independent work when the caller needs a complete report. It fulfills with one record per input, in input order. A fulfilled record has { status: 'fulfilled', value }; a rejected record has { status: 'rejected', reason }.
const outcomes = await Promise.allSettled([
Promise.resolve('cache hit'),
Promise.reject(new Error('service offline')),
]);
for (const outcome of outcomes) {
if (outcome.status === 'fulfilled') {
console.log(outcome.value);
} else {
console.log(outcome.reason.message);
}
}
Input rejections become data rather than rejecting the combined promise. The Promise.allSettled() algorithm can still produce a rejected promise if obtaining or stepping through the iterable fails, or if custom Promise subclass machinery throws while setting up the inputs. It is not a blanket conversion of every possible failure into a status record.
Waiting for every operation is also a tradeoff. One input that never settles keeps the report pending, even if every other outcome is already known.
Promise.any keeps the first success
Use Promise.any() for interchangeable attempts where one success is enough. Rejections are ignored until either an input fulfills or every input has rejected. In the all-rejected case, the result rejects with an AggregateError.
The errors array follows input order, not rejection order. This example rejects B first but reports A first:
const candidates = [
new Promise((_, reject) => {
setTimeout(() => reject(new Error('A')), 30);
}),
new Promise((_, reject) => {
setTimeout(() => reject(new Error('B')), 5);
}),
];
try {
await Promise.any(candidates);
} catch (error) {
console.log(error instanceof AggregateError); // true
console.log(error.errors.map(({ message }) => message)); // ['A', 'B']
}
The ordering follows the indexed error slots in the ECMAScript Promise.any() algorithm. This is useful when each input position identifies a server or strategy.
Promise.race observes the first settlement
Use Promise.race() when the first fulfillment or rejection reaction should decide the combined result. It does not prefer success. A fast rejection beats a later fulfillment. If several inputs are already settled, reactions are queued as the iterable is consumed, so the earliest input normally wins.
A timeout implemented with Promise.race() changes what the caller awaits, but it does not stop the operation that lost:
const events = [];
const work = new Promise((resolve) => {
setTimeout(() => {
events.push('work finished');
resolve('result');
}, 30);
});
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error('timeout')), 5);
});
await Promise.race([work, timeout]).catch((error) => {
events.push(error.message);
});
await work;
console.log(events); // ['timeout', 'work finished']
The Promise.race() specification returns the new promise without resolving it when the iterable is empty. Code that may receive an empty collection must handle that case before awaiting the race.
Cancellation belongs to the operation
Promise combinators have no general way to reverse a file write, close a connection, or decide whether shared work is safe to stop. The WHATWG DOM cancellation model states that Promise APIs do not have built-in abortion provisions. An operation that supports cancellation can instead accept an AbortSignal and define how it reacts.
For a timeout or first-success strategy, create the signal before starting the operations, pass it to each operation that supports it, then abort the losing work after the combined promise settles. This is explicit coordination between your code and the producers. Adding Promise.race() or Promise.any() by itself only settles a result promise.
Choose all for an all-or-nothing result, allSettled for a complete outcome report, any for the first success, and race for the first settlement. Then make a separate cancellation decision based on what the underlying operations can safely stop.