skip to content

Promise.race and Promise.any

race settles with the first promise to settle either way, while any waits for the first fulfillment and rejects with an AggregateError only when every input fails. Interviewers like race because it is how you build a timeout — and how you leak the loser.

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

questions

3

In JavaScript, what does the promise returned by Promise.race(iterable) settle with, and what happens if the input that settles first is a rejection?

level: juniorimportance: must knowfreq 68%

answer

  1. first past the post
  2. not first success
  3. settles either way, rejection included
  4. empty iterable never settles
  5. losers keep running, nothing cancelled

basics

~20 s

Promise.race settles with the first input promise to settle and copies its outcome either way. If that first settled input rejects, the race rejects with the same reason, even when other inputs would have fulfilled.

solid answer

~40 s

`Promise.race` takes an iterable, subscribes to every entry, and settles the returned promise with whichever entry settles first — adopting that entry's outcome exactly. It is first-past-the-post, not first-success: if the earliest settlement is a rejection, the race rejects with that reason and the later fulfilments are ignored. Non-promise entries are wrapped with `Promise.resolve`, so a plain value in the list effectively wins immediately. An empty iterable produces a promise that stays pending forever, because nothing can ever settle it. Nothing is cancelled when the race resolves — the losing promises keep running, their results are simply discarded. If you want the first *successful* outcome instead, that is what `Promise.any` is for.

code

javascript · 10 lines
javascript
const slow = new Promise((resolve) => setTimeout(() => resolve('slow ok'), 50));
const fastFail = new Promise((_, reject) =>
  setTimeout(() => reject(new Error('boom')), 10),
);

Promise.race([slow, fastFail])
  .then((v) => console.log('fulfilled:', v))
  .catch((e) => console.log('rejected:', e.message)); // rejected: boom

Promise.race([slow, 'plain value']).then((v) => console.log(v)); // plain value

go deeper

for a junior

Be able to say plainly that race settles with the first input to settle and copies its outcome, so an early rejection rejects the race. Know that plain values are allowed in the list.

for a middle

Explain the mechanics: every entry is passed through Promise.resolve and given the same resolve/reject pair, a promise settles only once, and among already-settled entries iteration order decides. Know that an empty iterable never settles.

for a senior

Show production judgment about what race does not give you: no cancellation of losers, no identity of the winner, and a fast failure that masks a slow success. Be ready to say when you would reach for a different combinator instead.

for a principal

Own the design tradeoff of bolting deadlines onto promises with race at all — the leaked in-flight work, the resources it holds, and when a request-level abort mechanism or a server-side deadline is the honest answer rather than a client-side cutoff.

## The one-sentence rule `Promise.race(iterable)` returns a new promise that settles the moment the **first** entry of the iterable settles, and it adopts that entry's outcome verbatim — fulfilled with its value, or rejected with its reason. "Race" is literal: it is first past the post, and finishing first is not the same as finishing well. ## Why the rejection case surprises people The name suggests "give me the first answer", and people read *answer* as *success*. The spec does not agree. Internally the combinator attaches the same pair of handlers to every entry — the resolve function and the reject function of the promise it hands back: ```javascript const first = Promise.race([ new Promise((resolve) => setTimeout(() => resolve('slow ok'), 50)), new Promise((_, reject) => setTimeout(() => reject(new Error('fast fail')), 10)), ]); first.catch((e) => console.log(e.message)); // fast fail ``` The fast rejection reaches the reject function first, so the race rejects. The slow fulfilment arrives 40 ms later and calls the resolve function, which is now a no-op: a promise settles once and cannot be re-settled. Practical consequence — **a fast failure hides a slow success**. If you race three mirrors of the same resource hoping for the quickest good answer, one mirror that fails instantly wins the race and your whole call fails. `Promise.any` exists precisely for that case: it waits for the first *fulfilment*. ## What "first" means for values already settled Every entry is passed through `Promise.resolve` (strictly, through the constructor's `resolve` method), so the iterable may hold plain values and thenables, not just promises: ```javascript Promise.race([fetch('/slow'), 'sentinel']).then(console.log); // sentinel ``` A plain value resolves in the next microtask turn, so nothing that involves real I/O can beat it. When two entries are *already* settled, the winner is the earlier one in iteration order, because reactions are queued in that order. This makes such snippets deterministic, which is exactly why interviewers use them. ## The empty iterable `Promise.race([])` returns a promise that is **pending forever**. There is no entry to settle it and no default outcome, so `await Promise.race([])` inside an async function simply never continues. If the array of candidates is built dynamically — filtered mirrors, filtered pending requests — guard against it being empty; a silently hung request is much harder to diagnose than a thrown error. ## The everyday use: timeouts The reason `race` is on every interviewer's list is that it is how you bolt a deadline onto a promise that has no deadline of its own: ```javascript const timeout = (ms) => new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), ms)); const result = await Promise.race([doWork(), timeout(2000)]); ``` The timeout branch is a promise that only ever rejects, so either the work wins and you get its value, or the clock wins and you get an error. This works precisely *because* race honours rejections. ## What race does not do It does not cancel. When the race settles, the losing promises are still running; their eventual values are dropped on the floor, but their side effects — the network request completing, the row being written, the timer still pending — are not undone. A promise is a handle on a result, not a handle on the work, so no combinator built out of promises can stop anything. Cancelling for real requires the underlying operation to expose its own abort mechanism. One related nuance worth knowing: because `race` attached a rejection handler to *every* entry, a loser that rejects after the race has settled is still considered **handled**. It will not fire an unhandled-rejection warning, even though nobody visibly caught it. ## Reading a race result The returned value tells you *what* won, never *which entry* won. If you need the winner's identity, tag each entry before racing: ```javascript const tagged = sources.map((p, i) => p.then((value) => ({ i, value }))); const { i, value } = await Promise.race(tagged); ``` That is a plain chaining trick, not a feature of the combinator — a good sign you understand that `race` returns a value, not a promise identity.

  • What does Promise.race([]) do, and why would that show up as a bug?
    It returns a promise that stays pending forever: there is no entry that could ever settle it and the combinator has no default outcome. Awaiting it silently stalls the async function with no error and no stack — the request just never finishes. When the candidate list is built by filtering, check for an empty array first and fail explicitly instead.
  • You race several mirrors hoping for the fastest good response. Why is race the wrong combinator there?
    Because race adopts the first settlement of any kind, so the mirror that fails fastest wins and the whole call rejects while healthy mirrors are still in flight. `Promise.any` is the right tool: it ignores rejections until every input has failed, and only then rejects with an `AggregateError`. Race is for deadlines and cutoffs, not for redundancy.
  • Does a losing promise that rejects after the race settled trigger an unhandled rejection?
    No. `Promise.race` attaches both a fulfilment and a rejection handler to every entry, so each entry's rejection is handled by the combinator. Calling the already-settled race's reject function is a no-op, and the runtime never reports it. A promise you created but did *not* pass into the race is a different story — that one is genuinely unhandled.

It is a photo finish, not a judging panel: whoever crosses the line first wins, even if they cross it face-first.

saying these in an interview costs you the question

  • Says race waits for the first successful promise
  • Thinks a later fulfilment can flip a race that already rejected
  • Believes Promise.race([]) fulfils with undefined
  • Assumes losing promises are cancelled when the race settles
  • Claims non-promise values in the iterable are skipped

context

open as a page

How does Promise.any decide what to settle with, and what exactly is the rejection value when every promise you passed it fails?

level: middleimportance: should knowfreq 52%

basics

~20 s

Promise.any fulfils with the value of the first input promise to fulfil, ignoring rejections along the way. Only if every input rejects does it reject, with an AggregateError whose errors property holds every reason in input order.

open as a page

You add a deadline to a request with Promise.race([work(), rejectAfter(5000)]). When the timeout branch wins, what actually happens to the work promise and to the pending setTimeout, and what problems does that cause in a long-running service?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Nothing stops. The race rejects, but the work promise keeps running to completion with its result discarded, its side effects still landing, and the timer stays scheduled whenever the work wins instead. Promise combinators end the wait, not the work.

open as a page