Topic 10.7
Promise.all, allSettled, race and any
In one line
Four static methods combine many promises into one: all waits for every one (and fails fast), allSettled waits for every outcome, race takes the first to settle, and any takes the first success.
Think of it like this
Four ways to wait for friends at a cinema. all: "We go in when everyone is here, and if anyone cancels, the plan is off." allSettled: "We wait until we've heard from everyone, coming or not, then decide." race: "Whatever happens first, the first arrival or the first cancellation, decides." any: "We go in with the first person who actually turns up; only if everyone cancels do we give up."
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Combinator
- A function that combines several promises into one new promise.
- Parallel (here)
- Several async jobs waiting at the same time, instead of one after another.
- Fail-fast
- Stop waiting and report an error as soon as the first job fails.
- Result object
- What
allSettledgives for each promise:{ status, value }or{ status, reason }. - AggregateError
- An error holding a list of several errors in its
errorsproperty;Promise.anyuses it when everything fails. - Iterable
- Anything
for...ofcan loop over, such as an array, a Set or a string.
Step by step
01Start everything first
Calling the three functions starts all three jobs at once. Each returns a promise straight away. Nothing waits yet.
const userP = getUser(); // starts
const ordersP = getOrders(); // starts
const prefsP = getPrefs(); // starts02Wait for all of them
Promise.all gives you one promise for the three. The total wait is about the time of the slowest job, not the sum.
Promise.all([userP, ordersP, prefsP])
.then(([user, orders, prefs]) => render(user, orders, prefs))
.catch((err) => showError(err)); // the first failure03Keep partial results with allSettled
If the orders service is down, all throws away the user and prefs too. allSettled gives you all three outcomes so you can show what worked.
const results = await Promise.allSettled([userP, ordersP, prefsP]);
for (const r of results) {
if (r.status === "fulfilled") use(r.value);
else console.warn("Part failed:", r.reason.message);
}04Add a deadline with race
Race the real job against a timer that rejects. Whichever settles first wins.
function timeout(ms) {
return new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timed out after " + ms + " ms")), ms),
);
}
Promise.race([getOrders(), timeout(2000)]);05Take the first success with any
Ask three mirrors for the same file; use whichever answers successfully first. Only if all three fail do you get an AggregateError.
Try it yourself
- 1
Make the slow job fail too
In the first example, change
wait(300, "slow")tofail(300, "slow failed"). Does the output change? Why not? - 2
Swap allSettled for all
In the second example, change
allSettledtoalland add.catch((e) => console.log("failed:", e.message)). Predict what the page shows now. - 3
Let the server win
In the third example, change the race's
wait(400, ...)towait(50, ...). Predict the race line, then check.
Code & diagrams
The array is in input order even though `settings` finished first. The second `all` rejected at 100 ms, without waiting for the slow job.
Preview
Console
Nothing logged yet.Expected console output
all rejected fast: orders service down
all fulfilled: [ 'user', 'orders', 'settings' ]One failure didn't stop the other two parts from showing.
Preview
Console
Nothing logged yet.Expected console output
fulfilled Profile
rejected Orders unavailable
fulfilled Messages
{ status: 'rejected', reason: Error: Orders unavailable }`race` took the first to settle, a rejection. `any` skipped mirror 1's failure and took the first success. When every promise failed, `any` rejected with an `AggregateError` listing them all.
Preview
Console
Nothing logged yet.Expected console output
race: Timed out after 100 ms
any: file from mirror 2
AggregateError: All promises were rejected [ 'a failed', 'b failed' ]Passing something that isn't iterable doesn't throw on the spot: the returned promise rejects with a `TypeError`. It prints before the plain-values line because the empty and broken inputs settle at once, while `all` needs a few microtask turns to collect three values.
Preview
Console
Nothing logged yet.Expected console output
all([]): []
allSettled([]): []
any([]): AggregateError
not an array: TypeError
plain values are fine: [ 1, 'two', 3 ]Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Await in a loop when the jobs are independent
Load 5 independent items with for (const id of ids) { results.push(await load(id)); } where each takes 300 ms.
Break #2
Pass promises as separate arguments
Write Promise.all(getUser(), getPosts()).
Myth vs fact
Myth
Promise.all runs the promises in parallel.
Fact
Promises don't run; the operations run as soon as you call the functions that start them. all only waits. If you start them one at a time (awaiting each), all can't make it parallel.
Myth
When Promise.all rejects, the other operations are cancelled.
Fact
They keep running and their results are ignored. Cancel real work with AbortController if it matters.
Myth
race and any are the same.
Fact
race settles with the first promise to settle, even a failure. any waits for the first success and only rejects if all fail.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Fan-out with
Promise.all(items.map(fetchOne))can fire hundreds of requests at once and overload a server or hit the browser's connection limit (about 6 per host on HTTP/1.1). Limit concurrency with a small pool: process in batches, or keep N workers pulling from a shared queue. - ▸
If one promise in a
Promise.allrejects, the others may reject later too, but those later rejections are already "handled" byall, so they don't produce unhandled-rejection noise.allSettledis still clearer when you need every outcome. - ▸
A
race-based timeout leaves its timer running after the real job wins. In long-lived pages, clear it: keep the timer id andclearTimeoutin afinally. Forfetch, preferAbortSignal.timeout(ms), which actually cancels the request. - ▸
Combinators read their input with the iteration protocol and call
Promise.resolveon each item (actuallythis.resolve, so subclasses work). That's why plain values and thenables are accepted, and why a generator can feed them.
Remember this
- 1
All four take an iterable (usually an array) of promises (plain values are treated as already-fulfilled promises) and return one new promise. They don't start any work: the operations already started when you called the functions that created the promises. The combinator only decides how to wait for them. That's the key to running things in parallel: start them all, then combine.
- 2
Promise.all(promises)(ES2015) fulfils with an array of values in the same order as the input, no matter which finished first, once all have fulfilled. If any rejects, it rejects immediately with that first error (fail-fast) and you don't get the other values. The other operations still carry on running; they're not cancelled, their results are ignored.Promise.all([])fulfils at once with[]. - 3
Promise.allSettled(promises)(ES2020) never rejects. It waits for every promise to settle and fulfils with an array of result objects:{ status: "fulfilled", value }or{ status: "rejected", reason }. Use it when the jobs are independent and partial success is useful, such as loading the parts of a dashboard. - 4
Promise.race(promises)(ES2015) settles the same way as the first promise to settle, fulfilled or rejected. Its classic use is a timeout: race the real job against a timer that rejects.Promise.race([])stays pending forever. Note that losing the race doesn't stop the slower job: forfetch, use anAbortSignalto really cancel it (Topic 11.2). - 5
Promise.any(promises)(ES2021) fulfils with the first success and ignores failures, unless every promise rejects. Then it rejects with an **AggregateError**, a special error whoseerrorsproperty is an array of all the reasons. Use it for redundancy, such as trying several mirrors of the same file.Promise.any([])rejects at once. - 6
Choosing: need everything or nothing →
all. Need to report on everything →allSettled. Need the fastest answer or a deadline →race. Need any one success →any. All four also exist withawait(Topic 10.8):const [user, posts] = await Promise.all([getUser(), getPosts()]);is one of the most common lines in modern JavaScript.
Explain it without notes
Compare Promise.all, allSettled, race and any: what does each fulfil and reject with?
If Promise.all makes nothing run in parallel, why does using it make code faster?
How would you add a timeout to a promise, and what is the limitation of doing it with race?
What is an AggregateError, and when do you see one?
Practice
Using wait(ms, value), load "A" (100 ms), "B" (200 ms) and "C" (50 ms) in parallel and log [ 'A', 'B', 'C' ].
Write firstAnswer(promises) that fulfils with the first success, or with the string "no answer" if every promise fails.
Trade-offs
- ↔
allgives a simple all-or-nothing result but throws away partial successes;allSettledkeeps every outcome but leaves you to inspect statuses and decide what counts as failure. - ↔
Running everything in parallel is fastest for a few jobs; for many, it can overload servers and the network, so a concurrency limit trades a little speed for reliability.
Done when you can
I start independent jobs together and combine them with
Promise.all.I know
allkeeps input order and rejects on the first failure.I use
allSettledwhen partial results are useful.I can build a timeout with
raceand know it doesn't cancel the job.I can explain
anyandAggregateError.