DEV Community

Mohsen Fallahnejad
Mohsen Fallahnejad

Posted on Edited on Originally published at bitlyst.blog

Promise.all vs race vs any vs allSettled: When Do You Stop Waiting?

Picture a page that loads a user, their orders and their recommendations with Promise.all. The recommendations service hiccups, and the whole page shows an error, even though the user and orders loaded fine.

Nothing is broken. You asked a question with the wrong rule for waiting.

The mental model: four rules for when to stop waiting

Picture yourself waiting for friends to reply in a group chat about dinner. All four Promise APIs send the message to everyone at once. They differ only in when you stop checking the chat:

  • all: wait until everyone says yes. If anyone says no, the dinner is off right away.
  • race: the first reply decides, yes or no.
  • any: the first yes decides. You only give up if everyone says no.
  • allSettled: wait until everyone has answered, then read the full list.

Choose a Promise combinator by what should count as failure, not by speed. They all run the work concurrently; the difference is when you stop waiting.

How each one decides

Promise.all: all or nothing. Resolves with every value in order, or rejects with the first error.

const [user, orders] = await Promise.all([getUser(), getOrders()]);
await Promise.all([ok(), fail(), ok()]); // rejects with fail()'s error
Enter fullscreen mode Exit fullscreen mode

Promise.race: first to settle wins, whether it succeeded or failed. That makes it the classic timeout:

await Promise.race([fetch("/data"), timeout(3000)]); // whichever finishes first
Enter fullscreen mode Exit fullscreen mode

Promise.any: first success wins. Failures are ignored until all of them fail; then it rejects with an AggregateError that holds every reason.

await Promise.any([fail(), ok("cdn-2"), ok("cdn-3")]); // "cdn-2"
Enter fullscreen mode Exit fullscreen mode

Promise.allSettled: never rejects. It waits for everyone and gives you a report:

await Promise.allSettled([ok(1), fail()]);
// [{ status: "fulfilled", value: 1 }, { status: "rejected", reason: Error }]
Enter fullscreen mode Exit fullscreen mode

In practice

Back to the dinner chat: ask yourself who's allowed to say no.

You need… Use
Every result, or nothing makes sense all
A deadline or "fastest answer, good or bad" race
One working answer from several backups any
As many results as possible, failures included allSettled

For the page in the hook, the user and orders are required, but recommendations are optional. So split them: all for the required pair, and handle recommendations on their own (or use allSettled and render what came back).

The honest stakes: all is right most of the time. The bugs show up when you use it for optional data, or use race when you meant "first success" (a fast failure wins the race and you reject too early).

Say it in an interview

"All four start every promise at once; none of them make JavaScript parallel. They only differ in when they stop waiting. all needs everyone to succeed and fails on the first error. race takes the first result, success or failure, so it's good for timeouts. any takes the first success and only fails if all fail. allSettled never fails; it waits for everyone and reports each outcome."

Cheat sheet

API Stops waiting when Rejects?
all everyone succeeds, or first failure yes, first error
race first one settles if the first one failed
any first success only if all fail (AggregateError)
allSettled everyone settles never
  • Same start, different stop rule.
  • Pick by who's allowed to fail.
  • race ≠ any: a fast failure wins a race.

Going deeper

Advanced and optional. Everything above is enough to explain this in an interview. This part is for the curious: full numbers, edge cases and what's still unproven.

Stopping early doesn't cancel anything

When all rejects or race settles, the other promises keep running. Their requests still go out, and their results are just ignored. To actually stop the work, pass an AbortController signal to fetch and call abort() once you have your answer. See AbortController, a practical guide.

Sequential vs concurrent

The combinators only help if you start the work before you wait for it:

await a(); await b();              // sequential: b starts after a finishes
await Promise.all([a(), b()]);     // concurrent: both start right away
Enter fullscreen mode Exit fullscreen mode

Why this works on a single thread is its own story: How Promise.all works in a single-threaded world.

Edge cases

  • Empty input: all([]) and allSettled([]) resolve to [], any([]) rejects with an AggregateError, and race([]) stays pending forever.
  • Non-promise values are wrapped with Promise.resolve, so Promise.all([1, fetchX()]) works.
  • Promise.any and AggregateError are ES2021; allSettled is ES2020. Both are in every current browser and Node 16+.

Originally published on Bitlyst.

Top comments (0)

⌂ 换网址