← blog
JavaScriptJuly 13, 2026 · 5 min read

You reach for Promise.all for every concurrent request. Here's when to use the other three.

`Promise.all` fails the moment any one request fails. `allSettled`, `any`, and `race` each solve a different concurrency problem — dashboard partial results, CDN failover, and timeouts. Here's the taxonomy.

Parsa Jiravand · Frontend engineer · building bestpractic
You reach for `Promise.all` for every concurrent request. Here's when to use the other three.

Imagine you're loading a dashboard. Four widgets, four APIs, fire them all at once:

JavaScript
1
2
3
4
5
6
const [users, revenue, alerts, activity] = await Promise.all([ fetchUsers(), fetchRevenue(), fetchAlerts(), fetchActivity(), ]);

The alerts API is occasionally slow and sometimes returns a 500. When it does, your entire dashboard fails. Not one broken widget — four broken widgets. Promise.all rejects on the first failure and takes the other three successful results with it into the void.

You reached for the right primitive for concurrency, but the wrong one for this use case.

JavaScript gives you four ways to run promises concurrently. They differ in one thing: what happens when a promise fails or resolves first.

MethodResolves whenRejects when
Promise.allAll succeedAny one fails
Promise.allSettledAll finish (success or failure)Never
Promise.anyAny one succeedsAll fail
Promise.raceAny one finishesAny one fails first

The instinct to reach for Promise.all is understandable — it returns all the values in a single array and feels like the obvious way to "do these things at the same time." But concurrency and failure handling are two separate questions. Promise.all answers both at once, and the answer to the second one is often wrong for the situation you're in.

Runs right in your browser — poke at it and watch the concept react live.

Promise.allSettled waits for every promise to settle — resolve or reject — and returns a result array describing what happened to each one:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const results = await Promise.allSettled([ fetchUsers(), fetchRevenue(), fetchAlerts(), fetchActivity(), ]); for (const result of results) { if (result.status === 'fulfilled') { console.log('got data:', result.value); } else { console.error('failed:', result.reason); } }

Each element has a status of 'fulfilled' (with a value) or 'rejected' (with a reason). No promise can cancel any other. This is the right primitive for your dashboard: three working widgets stay working even when the fourth API is down.

The typical UI pattern is to destructure and null-check independently:

JavaScript
1
2
3
4
5
6
7
const [usersResult, revenueResult] = await Promise.allSettled([ fetchUsers(), fetchRevenue(), ]); const users = usersResult.status === 'fulfilled' ? usersResult.value : null; const revenue = revenueResult.status === 'fulfilled' ? revenueResult.value : null;

Render each widget or its error state based on its own result. One API being down no longer bleeds into another widget's data.

Promise.any resolves with the value of whichever promise succeeds first. It only rejects if all promises fail — and even then, the rejection is an AggregateError that wraps all the individual failure reasons.

The use case is CDN failover: you have two or three endpoints and want the fastest response that actually works.

JavaScript
1
2
3
4
5
const data = await Promise.any([ fetch('https://cdn1.example.com/data.json'), fetch('https://cdn2.example.com/data.json'), fetch('https://cdn3.example.com/data.json'), ]).then(res => res.json());

If cdn1 is slow and cdn2 responds first, you get cdn2's result immediately. If cdn1 responds later, it's discarded. If cdn1 and cdn2 both fail but cdn3 succeeds, you still get data.

The key distinction from Promise.race: a rejection from a fast server doesn't end Promise.any. It continues waiting for a success from the remaining sources. Promise.race stops at the first settled promise — a rejection is enough to close it. Promise.any stops at the first resolved promise. The two primitives are not interchangeable, and reaching for race when you want any gets you a CDN failover that fails as soon as your primary CDN sends any error.

Promise.race settles with whatever promise settles first — success or failure. The correct use case: imposing a timeout on an operation that doesn't have one built in.

JavaScript
1
2
3
4
5
6
7
8
function withTimeout(promise, ms) { const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error(`Timed out after ${ms}ms`)), ms) ); return Promise.race([promise, timeout]); } const data = await withTimeout(fetchLargeReport(), 5000);

If fetchLargeReport() resolves within 5 seconds, you get the data. If it takes longer, the timeout promise rejects first and Promise.race rejects with the timeout error.

Note that the original fetch continues running in the background after the race completes — the promise doesn't stop just because you stopped listening. Pair this with AbortController to actually cancel the request when the timeout fires.

Promise.all is not wrong — it solves a real and specific problem: operations where you need all results and partial success is not a meaningful state.

A form submission that must write to three services atomically: if any fails, the whole operation should fail and roll back. Fetching a user record and their permissions when you can't render the page with either one missing. Building a combined response from two sources where neither source alone produces a valid output.

The tell is whether partial success matters to your application. If one failure should stop everything, Promise.all is exactly right. If partial success is valid — and for most UI data fetching, it is — you want allSettled.

Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.

Before you write Promise.all, ask one question:

  • Yes, and I want all the results: Promise.allSettled
  • Yes, and I just want the fastest one that works: Promise.any
  • I want the first outcome, success or failure: Promise.race
  • No — one failure should fail everything: Promise.all

Promise.all was the first concurrent Promise method, so it's the one most developers reach for first. But the others aren't edge cases — they describe real, common failure modes that Promise.all handles badly. Next time you type Promise.all, spend one second on that question. You might mean one of the other three.


Thanks for reading! Let's stay connected:

Originally published on dev.to