In frontend interviews and daily development, Promise.race is a very common yet often overlooked API. Its rule is simple:
Multiple Promises “race” against each other—whichever settles first (fulfilled or rejected) determines the final result.
In this article, we will:
- Clearly explain how
Promise.racebehaves and when to use it - Implement a custom
myPromiseRacefrom scratch - Cover edge cases, optimizations, and common interview questions
1. What Is Promise.race?
Promise.race(iterable) accepts an iterable (usually an array) and returns a new Promise:
- If any Promise fulfills first, the returned Promise fulfills
- If any Promise rejects first, the returned Promise rejects
- It does not wait for all Promises to finish
- It does not cancel the remaining Promises—they continue running
2. Common Use Cases
2.1 Timeout Control (Most Common)
For example, treat a request as failed if it takes longer than 3 seconds:
fetch(url)vstimeoutPromise(3000)- Whichever finishes first determines the result
2.2 Multiple Data Sources (Fallback Strategy)
Fetch the same resource from multiple CDNs (CDN1/CDN2).
Use whichever responds first.
2.3 First-Render Racing
Load primary data, fallback data, and cached data simultaneously.
Render the page as soon as the fastest result arrives.
3. Implementing myPromiseRace
This implementation closely matches native behavior:
- Validates that the input is iterable
- Iterates over all inputs
- Uses
Promise.resolveto normalize non-Promise values - Resolves or rejects as soon as one Promise settles
1 | function myPromiseRace(promises) { |
4. Key Insight: Why Use Promise.resolve?
The input to Promise.race does not have to be Promises. It may include:
- Plain values:
Promise.race([1, 2, 3]) - Thenables:
{ then(resolve) { resolve(123) } }
Using Promise.resolve(x) ensures:
- Plain values become immediately fulfilled Promises
- Thenables are properly assimilated into the Promise chain
This behavior is consistent with the native Promise.race.
5. Handling Empty Iterables
Native
Promise.race([])returns a Promise that remains pending forever.
Your current implementation behaves the same way, since the loop never executes.
This matches the ECMAScript specification.
You could add explicit handling, but it’s not required.
6. Additional Test Cases
6.1 Rejection Wins First
1 | const p1 = new Promise(res => setTimeout(() => res('p1'), 1000)); |
6.2 Plain Values Participate in the Race
1 | myPromiseRace([ |
6.3 Empty Array Remains Pending
1 | const p = myPromiseRace([]); |
7. Practical Example: Request Timeout with race
1 | function timeout(ms) { |
8. Common Interview Questions (Bonus Points)
Q: Can
Promise.racecancel the other Promises?
No. Promises are not cancellable by default. Cancellation requires tools likeAbortControlleror custom logic.Q: What happens if you pass a plain value into
race?
It is wrapped byPromise.resolveand usually wins immediately.Q: What’s the difference between
race,any, andallSettled?race: settles as soon as the first Promise settlesany: fulfills as soon as the first Promise fulfills (rejects only if all reject)allSettled: waits for all Promises and returns their final states
9. Summary
The core idea behind implementing Promise.race can be summarized in one sentence:
Iterate over the iterable, wrap each item with
Promise.resolve, attach the sameresolveandreject, and let the first settled Promise decide the outcome.