Promise.race
Introduction
Promise.race(iterable) settles as soon as the first input settles, whether that input fulfils or rejects. It is the first-signal-wins combinator, commonly used for timeouts, fallback signals, and taking the earliest result when both success and failure should be decisive.
Why This Matters
race is a common source of wrong answers because candidates treat it like fastest success. It is not. A fast rejection rejects the race. A plain value can win before timer-backed promises. The losers are not cancelled automatically.
Theory
Exact semantics
Promise.race accepts an iterable of values, promises, or thenables. It settles with the outcome of the first input that settles. If the first settled input fulfils, the race fulfils with that value. If the first settled input rejects, the race rejects with that reason.
Plain values are treated as already fulfilled, so a plain value usually wins over promises that settle in future timer turns. If several already-settled promises are observed together, their reactions are queued in iteration order, so the earliest observed reaction wins.
For an empty iterable, there is no input that can settle, so Promise.race([]) returns a promise that remains pending forever.
Like all promise combinators, race does not cancel losers. A timeout race can reject quickly while the original operation continues unless the underlying API supports cancellation.
Combinator comparison
| Combinator | Fulfils when | Rejects when | Result shape | Typical use |
|---|---|---|---|---|
Promise.all | Every input fulfils | First input rejects | Array of values in input order | All-or-nothing parallel work |
Promise.any | First input fulfils | Every input rejects | First fulfilled value or AggregateError | Redundant sources, fastest success |
Promise.allSettled | Every input settles | Never because of input rejection | Array of status objects in input order | Partial success reporting |
Promise.race | First input fulfils if it settles first | First input rejects if it settles first | First settled value or reason | Timeouts, first signal wins |
Timeout pattern
The classic pattern races real work against a promise that rejects after a delay. This limits how long the caller waits, but it does not stop the real work by itself. In production, pair the timeout with a cancellation mechanism when possible.
Race vs any
Use race when the earliest signal should decide the outcome, including failure. Use any when failures are tolerable and you want the first success.
Visual Diagrams
A pending -------- fulfils later B pending ---- rejects first ---- Promise.race rejects with B reason C pending -------- fulfils later The first settled input decides the combined promise.
Race observes settlement, not success.
real work promise ---------------- maybe fulfils later
timeout promise ---- rejects first
|
v
Promise.race rejects with timeout reason
The real work is still running unless cancelled separately.A timeout race limits waiting; it does not automatically cancel work.
Code Examples
Build a timeout with Promise.race
The timeout rejects the race if the operation takes too long. Cancellation of the operation is separate.
Plain values can win the race
Values are normalised through promise resolution, so a non-promise input can settle the race before async work.
Playground
Press Run to execute the code and see output here.
Output Prediction
Predict the output #1
1const slow = new Promise(function (resolve) {2 setTimeout(function () {3 console.log('slow done');4 resolve('slow');5 }, 20);6});7 8const fast = new Promise(function (resolve, reject) {9 setTimeout(function () {10 console.log('fast failed');11 reject('fast');12 }, 10);13});14 15Promise.race([slow, fast])16 .then(function (value) {17 console.log('win ' + value);18 })19 .catch(function (reason) {20 console.log('lose ' + reason);21 });22 23console.log('sync');Predict the output #2
1Promise.race([2 new Promise(function (resolve) {3 setTimeout(function () {4 resolve('timer');5 }, 0);6 }),7 'plain',8]).then(function (value) {9 console.log(value);10});11 12console.log('sync');Coding Exercises
Implement a mini Promise.race
MediumImplement promiseRace(iterable): accept values or promises and settle with the first input to settle, whether fulfilled or rejected. Empty input should leave the returned promise pending.
Interview Questions
1Explain `Promise.race` and a common use case.
Promise.race returns a promise that settles with the first input to settle, whether fulfilled or rejected. A common use case is racing an operation against a timeout promise. If the timeout rejects first, the caller stops waiting, but the original operation is not automatically cancelled.
Follow-ups
- How is it different from `Promise.any`?
- What happens with `Promise.race([])`?
2Does `Promise.race` cancel the losing promises?
No. Promise.race only settles the combined promise with the first settlement. Losing promises continue to run and may still produce side effects. If cancellation matters, the underlying operation must support cancellation and you must trigger it explicitly.
Quiz
1. Which input wins `Promise.race`?
2. What does `Promise.race([])` do?
Summary
- `Promise.race` settles with the first input to settle, fulfilled or rejected.
- A fast rejection rejects the race; use `Promise.any` when you want fastest success instead.
- Plain values can win because inputs are normalised through promise resolution.
- `race` does not cancel losers, and `Promise.race([])` stays pending forever.
Cheat Sheet
Use when: first signal wins.
Fulfil: first settled input fulfils.
Reject: first settled input rejects.
Empty: pending forever.
Timeout pattern: Promise.race([work, timeout]).
Caveat: losers continue unless cancelled separately.