Promise.all, Promise.allSettled, Promise.race, and Promise.any look interchangeable in the docs. They're not. Each one has a different failure rule, and the wrong choice in production tends to fail in spectacular ways — usually on a Friday afternoon, usually at scale.
Promise.all Is All Or Nothing
Promise.all rejects on the first failure. The rest of the promises don't get cancelled — they just become invisible. You either get every value, or you get an error.
try {
const [user, orders, prefs] = await Promise.all([
fetchUser(id),
fetchOrders(id),
fetchPrefs(id),
]);
render(user, orders, prefs);
} catch (err) {
// any single failure → the whole thing fails
showError();
}
Use Promise.all when you genuinely cannot proceed without every result — checkout requires user, address, AND payment method; the dashboard renders meaningless without ALL three widgets.
The classic production bug: dev environment is fast and reliable, every promise resolves, the dashboard works. Production has one slow widget that times out, Promise.all rejects, the entire page goes blank. The fix is almost always Promise.allSettled.
allSettled Is For Partial UI
Promise.allSettled always resolves. You get an array of { status, value } or { status, reason } for each promise — letting you render what worked and show errors for what didn't.
const results = await Promise.allSettled([
fetchUser(id),
fetchOrders(id),
fetchPrefs(id),
]);
const get = <T,>(r: PromiseSettledResult<T>): T | null =>
r.status === 'fulfilled' ? r.value : null;
render({
user: get(results[0]),
orders: get(results[1]),
prefs: get(results[2]),
});
This is the right default for dashboards, search results that combine sources, recommendation widgets, and any UI where "show what worked" beats "show nothing." Failures become a UX choice instead of a crash.
race Is For First Result Or Timeout
Promise.race settles with whichever promise settles first — value or error. The classic use is timeouts:
const withTimeout = <T,>(promise: Promise<T>, ms: number): Promise<T> =>
Promise.race([
promise,
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
),
]);
const user = await withTimeout(fetchUser(id), 5000);
A first-error in the race rejects the whole thing — that's the point. If you want "first success, ignore failures," that's Promise.any, not race.
The other use case for race is querying multiple replicas of the same data: "give me the answer from whichever mirror responds first." When you don't care which source answers, just that you get one.
any Is For First Success
Promise.any resolves with the first promise that succeeds. If all of them reject, you get an AggregateError containing every reason.
try {
const data = await Promise.any([
fetchFromCDN1(url),
fetchFromCDN2(url),
fetchFromCDN3(url),
]);
use(data);
} catch (err) {
// all three CDNs failed
if (err instanceof AggregateError) console.log(err.errors);
showOfflineMessage();
}
Use Promise.any for fallback patterns: hit several mirrors, take the first one that works. Useful for resilience against partial outages, less useful for everyday business code where you usually want predictable behavior.
A Quick Decision Table
| Goal | Combinator | What you get |
|---|---|---|
| Need ALL values; one fail = fail | Promise.all |
Array of values, or first error |
| Want PARTIAL UI; failures are okay | Promise.allSettled |
Array of {status, value/reason} |
| First to settle (success OR error) | Promise.race |
First settled value, or first error |
| First SUCCESS only | Promise.any |
First fulfilled value, or AggregateError |
Most production bugs in this area are using all where allSettled was needed, or using race where any was needed.
Pro Tips
- Default to
allSettledwhen failures shouldn't block the UI. - Use
allfor atomic operations — checkout, transaction, all-or-nothing flows. racefor timeouts;anyfor first-success-among-mirrors.- Always handle the rejection. Even
allSettledcan leave you with all-failed cases the UI must communicate. - Log per-promise failures. Otherwise an
allSettledarray of errors disappears into the void.
Final Tips
The shortest version: pick the combinator that matches your failure rule. The next time a Promise.all blows up because one slow service was unavailable, you'll wish you'd used allSettled. The next time Promise.race rejects because the first response was an error, you'll wish you'd used any.
Don't read the names. Read the failure semantics.
Good luck — and may your dashboards keep rendering when one widget is sad 👊




