Command Palette

Search for a command to run...

Hectal
PHASE 10Intermediate ES2015+ ~20 min· topic 7 of 8

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 allSettled gives for each promise: { status, value } or { status, reason }.
AggregateError
An error holding a list of several errors in its errors property; Promise.any uses it when everything fails.
Iterable
Anything for...of can 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.

start.jswhole filejavascript
const userP = getUser();      // starts
const ordersP = getOrders();  // starts
const prefsP = getPrefs();    // starts

02Wait 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.

all.jswhole filejavascript
Promise.all([userP, ordersP, prefsP])
  .then(([user, orders, prefs]) => render(user, orders, prefs))
  .catch((err) => showError(err)); // the first failure
Wait for all of themdiagram
Rendering diagram…

03Keep 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.

settled.jswhole filejavascript
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.

timeout.jswhole filejavascript
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. 1

    Make the slow job fail too

    In the first example, change wait(300, "slow") to fail(300, "slow failed"). Does the output change? Why not?

  2. 2

    Swap allSettled for all

    In the second example, change allSettled to all and add .catch((e) => console.log("failed:", e.message)). Predict what the page shows now.

  3. 3

    Let the server win

    In the third example, change the race's wait(400, ...) to wait(50, ...). Predict the race line, then check.

Code & diagrams

Promise.all: values in input order, and fail-fast New tab

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

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

all rejected fast: orders service down
all fulfilled: [ 'user', 'orders', 'settings' ]
Promise.allSettled: every outcome ES2020+ New tab

One failure didn't stop the other two parts from showing.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

fulfilled Profile
rejected Orders unavailable
fulfilled Messages
{ status: 'rejected', reason: Error: Orders unavailable }
Promise.race for a timeout, Promise.any for the first success ES2021+ New tab

`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

Press Run to see the page.

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' ]
Edge cases: empty arrays, plain values and non-iterables New tab

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

Press Run to see the page.

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.

terminal
$ the page shows
── what you'll see ──
The list appears after about 1.5 seconds instead of about 0.3 seconds.

Break #2

Pass promises as separate arguments

Write Promise.all(getUser(), getPosts()).

terminal
$ Console
── what you'll see ──
Uncaught (in promise) TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))

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.all rejects, the others may reject later too, but those later rejections are already "handled" by all, so they don't produce unhandled-rejection noise. allSettled is 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 and clearTimeout in a finally. For fetch, prefer AbortSignal.timeout(ms), which actually cancels the request.

  • ▸

    Combinators read their input with the iteration protocol and call Promise.resolve on each item (actually this.resolve, so subclasses work). That's why plain values and thenables are accepted, and why a generator can feed them.

Remember this

  1. 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. 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. 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. 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: for fetch, use an AbortSignal to really cancel it (Topic 11.2).

  5. 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 whose errors property 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. 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 with await (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

01

Compare Promise.all, allSettled, race and any: what does each fulfil and reject with?

02

If Promise.all makes nothing run in parallel, why does using it make code faster?

03

How would you add a timeout to a promise, and what is the limitation of doing it with race?

04

What is an AggregateError, and when do you see one?

Practice

01

Using wait(ms, value), load "A" (100 ms), "B" (200 ms) and "C" (50 ms) in parallel and log [ 'A', 'B', 'C' ].

02

Write firstAnswer(promises) that fulfils with the first success, or with the string "no answer" if every promise fails.

Trade-offs

  • ↔

    all gives a simple all-or-nothing result but throws away partial successes; allSettled keeps 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 all keeps input order and rejects on the first failure.

  • I use allSettled when partial results are useful.

  • I can build a timeout with race and know it doesn't cancel the job.

  • I can explain any and AggregateError.