Promise.all() vs allSettled() vs race() Compared
Promise.all() fails fast, allSettled() waits for every result, and race() returns whichever promise finishes first — how to choose correctly.
Promise.all(), Promise.allSettled(), and Promise.race() all take an array of promises and return a single promise, but they disagree sharply on what “done” means. all() gives up the moment one promise rejects, allSettled() always waits for every promise regardless of outcome, and race() resolves or rejects the instant the first promise finishes, win or lose. Picking the wrong one is a common source of bugs that only show up when something you assumed would always succeed fails.
What each method actually returns
All three are static methods on the Promise constructor. They take an iterable of promises (or plain values, which get wrapped) and return one combined promise.
Promise.all(promises)resolves with an array of all the fulfilled values, in the same order as the input, once every promise has fulfilled. If any promise rejects, the returned promise rejects immediately with that error — the other promises keep running in the background, but their results are discarded.Promise.allSettled(promises)always resolves (never rejects) once every promise has either fulfilled or rejected. It resolves with an array of result objects, each shaped like{ status: "fulfilled", value }or{ status: "rejected", reason }.Promise.race(promises)settles — resolves or rejects — as soon as the first promise settles, adopting whatever that promise did. The rest keep running, but their outcomes are ignored.- A close cousin worth knowing:
Promise.any(promises)resolves with the first fulfillment, ignoring rejections, and only rejects if every promise rejects. It’s the optimistic counterpart torace().
Promise.all(): fail-fast, all-or-nothing
all() is the right tool when every result is required for the next step to make sense — fetching a user’s profile, their settings, and their permissions before rendering a dashboard, for example. If any one of those requests fails, there’s usually nothing sensible left to render, so failing fast and handling one error is simpler than partial data:
const [profile, settings, permissions] = await Promise.all([
fetch("/api/profile").then(r => r.json()),
fetch("/api/settings").then(r => r.json()),
fetch("/api/permissions").then(r => r.json()),
]);
The trade-off is visibility: a single rejection anywhere in the array hides whatever the other promises would have returned. If you need to know exactly which one failed and which succeeded, all() throws that information away.
Promise.allSettled(): every result, no matter what
allSettled() exists for exactly the case all() can’t handle: independent operations where a partial failure is still useful. Sending analytics events to three different endpoints, or fetching optional widgets for a page, are good fits — you want to know what worked and what didn’t, without one failure aborting the rest:
const results = await Promise.allSettled([
fetch("/api/widget-a"),
fetch("/api/widget-b"),
fetch("/api/widget-c"),
]);
const failures = results.filter(r => r.status === "rejected");
This is also the safer default when you’re not certain every promise will succeed and don’t want an unhandled rejection to propagate. Combined with a retry strategy — see our piece on exponential backoff — allSettled() is a natural place to decide which failed requests are worth retrying.
Promise.race() (and Promise.any())
race() is less about combining results and more about combining timing. The canonical use is a timeout: race a real request against a promise that rejects after a delay, and whichever finishes first wins.
const timeout = (ms) => new Promise((_, reject) =>
setTimeout(() => reject(new Error("timed out")), ms)
);
const data = await Promise.race([
fetch("/api/slow-endpoint"),
timeout(5000),
]);
It’s also useful for redundant sources of the same data — querying two mirrored APIs and taking whichever answers first. Just remember that “losing” promises in a race don’t stop running; if they have side effects (writes, mutations), those still happen even though their result is discarded. Promise.any() flips the failure semantics: it’s built for exactly that redundant-source case where you want the first success, not just the first response, and don’t want a single fast failure to win the race.
Comparison table
Promise.all() | Promise.allSettled() | Promise.race() | Promise.any() | |
|---|---|---|---|---|
| Settles when | All fulfill, or one rejects | All settle (always) | First one settles | First one fulfills |
| Can reject | Yes, on first rejection | Never | Yes, if the first to settle rejects | Only if all reject |
| Result shape | Array of values | Array of {status, value/reason} | The single winning value/error | The single winning value |
| Best for | All-or-nothing dependent tasks | Independent tasks, partial failure OK | Timeouts, first-response-wins | Redundant sources, want a success |
Choosing the right one in practice
Ask what “done” should mean for your specific operation. If the next step genuinely can’t proceed without every result, all() is correct and its fail-fast behavior is a feature, not a limitation. If the operations are independent and you’d rather have partial data than none, reach for allSettled(). If you’re racing against time or redundant sources rather than combining results at all, race() or any() fit. This same distinction matters in the JavaScript event loop: all four methods just register callbacks against the microtask queue and don’t block anything while they wait.
One related gotcha: Promise.race() and Promise.any() don’t cancel the losing promises — if you need to actually stop a slow fetch(), pair it with an AbortController rather than relying on race() alone to save resources. And no matter which combinator you use, remember it’s built on top of the same fundamentals covered in async/await vs. raw promises — the combinator only changes how multiple promises are aggregated, not how any individual one resolves. A race() used to implement a timeout is also a good reminder of why real race conditions are dangerous elsewhere in a codebase — here, the “race” is intentional and controlled, which is exactly what makes it safe.
The takeaway
The four Promise combinators encode four different answers to “when am I done, and what counts as failure.” all() is all-or-nothing and fails fast. allSettled() never rejects and hands back every outcome. race() adopts whatever finishes first, success or failure. any() waits for the first success and only fails if everything does. Match the method to what your calling code actually needs to happen next, and most bugs around partial failures and premature rejections disappear on their own.
Tagged
Keep reading
Takina · · 4 min read JavaScript Spread vs Rest Operators, Explained
The spread operator (...) expands an iterable into individual elements; the rest operator collects elements back into an array. Same syntax, opposite jobs.
Takina · · 4 min read ResizeObserver API Explained: Watching Element Size
The ResizeObserver API lets JavaScript watch an element's box size and react without polling or resize-event hacks. How it works and when to use it.
Takina · · 4 min read localStorage vs sessionStorage vs Cookies
localStorage, sessionStorage, and cookies all store data in the browser, but differ in lifetime, size limits, and whether the server can see them.