How to Use Promise Race in JS: Complete Guide
Promise.race settles as soon as the first promise in an array settles. Learn how to use it for timeouts, fastest-response patterns, and the key differences from Promise.any.
In JavaScript, Promise.race takes an array of promises and returns a new promise. That new promise settles the moment the first promise in the array settles. If the first one resolves, the race resolves with that value.
If the first one rejects instead, the race rejects with that same error. Think of it like a literal race: the fastest runner wins, and the race ends the instant the first person crosses the finish line, win or fail.
const fast = new Promise((resolve) => setTimeout(() => resolve("Fast"), 500));
const slow = new Promise((resolve) => setTimeout(() => resolve("Slow"), 2000));
Promise.race([fast, slow]).then((winner) => {
console.log(`Winner: ${winner}`);
});Running this logs the winner after 500 milliseconds, since the fast promise settles first and the race does not wait for the slow one.
Winner: FastThe slow promise still resolves two seconds later, but nobody is listening anymore. The race already finished.
Promise.race syntax
The method takes a single iterable, usually an array, and returns a promise you can await or chain with then, exactly like any other promise.
const result = await Promise.race(iterable);| Parameter | Description |
|---|---|
| iterable | An iterable (usually an array) of promises |
| Returns | A promise that settles with the first settled promise's value or error |
If the iterable contains non-promise values, they are wrapped in a resolved promise automatically. A plain value races as an instantly-resolved promise.
const instant = "I am ready now";
const delayed = new Promise((resolve) => setTimeout(() => resolve("Delayed"), 3000));
Promise.race([instant, delayed]).then(console.log);
// "I am ready now" (prints immediately)Adding a timeout to any async operation
The most common use of this method is adding a timeout to an async operation. Race the actual work against a promise that rejects after a time limit.
function fetchWithTimeout(url, timeoutMs = 5000) {
const fetchPromise = fetch(url).then((res) => res.json());
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error(`Request timed out after ${timeoutMs}ms`)), timeoutMs);
});
return Promise.race([fetchPromise, timeoutPromise]);
}Calling the function below shows the timeout in action: the try block succeeds when the fetch wins, and the catch block runs when the timer wins instead.
async function loadData() {
try {
const data = await fetchWithTimeout("https://api.example.com/slow-endpoint", 3000);
console.log("Data loaded:", data);
} catch (err) {
console.error("Request failed:", err.message);
}
}If the fetch completes within 3 seconds, the data is returned. If it takes longer, the timeout rejects and the error is caught instead. This pattern works with any promise-based operation: database queries, file reads, image loading, or third-party API calls.
Picking the fastest data source
When you have multiple equivalent data sources, racing them lets you take whichever responds first instead of always waiting on one fixed endpoint.
const primaryAPI = fetch("https://primary.api.example.com/data");
const fallbackAPI = fetch("https://fallback.api.example.com/data");
async function getFastestData() {
try {
const response = await Promise.race([primaryAPI, fallbackAPI]);
return response.json();
} catch (err) {
console.error("Both sources failed or winner rejected:", err.message);
}
}This only catches the case where the winner itself rejects. If a fast source fails while a slower one would have succeeded, Promise.any handles that case better, since it keeps waiting past rejections instead of ending the race on the first one. This pattern is useful for reducing latency when you have mirrors, CDN edges, or redundant services, since the user gets the fastest response without waiting for the slower source.
Promise.race vs Promise.any
These two combinators look similar but behave differently when rejections happen. The diagram below shows both settlement paths side by side.
Race stops at the first settlement no matter what it is, while any ignores rejections and keeps waiting until something resolves or every promise has rejected.
const failingFast = Promise.reject(new Error("I failed fast"));
const succeedingSlow = new Promise((resolve) => setTimeout(() => resolve("Success"), 1000));
Promise.race([failingFast, succeedingSlow])
.then(console.log)
.catch((err) => console.error("Race:", err.message));
// "Race: I failed fast"Racing the same two promises with any instead produces the opposite outcome, because the early rejection gets ignored rather than ending the operation.
Promise.any([failingFast, succeedingSlow])
.then(console.log)
.catch((err) => console.error("Any:", err.message));
// "Success" (after 1 second)| race | any | |
|---|---|---|
| Settles with | First settled, any outcome | First resolved, ignores rejections |
| All reject | Rejects with first error | Rejects with AggregateError |
| Empty array | Pending forever | Rejects with AggregateError |
| Use case | Timeouts, first-response wins | Fastest successful response |
Choose race when the first response matters regardless of success. Choose any when you want the first successful response and can tolerate some failures along the way.
Racing multiple fetch requests with AbortController
Since a race does not cancel the losing promises, those promises still consume resources after the race is decided. For fetch requests, use AbortController to cancel the losers once the winner arrives.
async function fetchFastest(urls) {
const controllers = urls.map(() => new AbortController());
const fetchPromises = urls.map((url, i) =>
fetch(url, { signal: controllers[i].signal }).then((res) => res.json())
);
const result = await Promise.race(fetchPromises);
controllers.forEach((ctrl) => ctrl.abort());
return result;
}Each request gets its own controller, and once the race settles, every other controller is aborted to free up the connection it was holding. Aborting an in-flight fetch makes it reject, so if a losing request happens to win a later race by coincidence, that abort needs to be handled too.
async function fetchFastestSafely(urls) {
try {
return await fetchFastest(urls);
} catch (err) {
if (err.name === "AbortError") return null;
throw err;
}
}This pattern is specific to fetch and other APIs that support an abort signal. Not all promise-based operations can be cancelled once they have started.
Common Promise.race mistakes
Using race when you want the first success. If one of your promises might reject but you still want a successful result, reach for any instead. With race, a single fast rejection kills the entire operation even when a slower promise would have succeeded.
// Mistake: a fast 500 error kills the race
Promise.race([
fetch("https://broken.api.example.com"), // Rejects quickly with 500
fetch("https://reliable.api.example.com"), // Would have succeeded
]);Swapping the method for any ignores that early rejection and keeps waiting for a source that actually succeeds, which is exactly the outcome the original code was hoping for when it reached for race by mistake.
// Better: Promise.any ignores the rejection
Promise.any([
fetch("https://broken.api.example.com"),
fetch("https://reliable.api.example.com"),
]);Forgetting that losing promises keep running. A race does not stop or cancel anything on its own. A losing timer still fires, a losing fetch still completes, and the result is simply ignored unless you cancel it yourself.
Assuming the race winner is always the fastest promise. If two promises resolve in the same microtask tick, the one with the lower array index wins. This is rarely relevant in practice but matters when racing promises that resolve synchronously.
const a = Promise.resolve("A");
const b = Promise.resolve("B");
Promise.race([a, b]).then(console.log); // "A" (lower index)
Promise.race([b, a]).then(console.log); // "B" (lower index)When to use Promise.race
A few situations come up often enough to be worth naming directly:
- Request timeout. Race a fetch call against a delayed rejection so a slow endpoint cannot hang the UI forever.
- Fastest CDN or mirror. Race identical requests to different endpoints and use whichever one answers first.
- User interaction timeout. Race a user action promise against a timer to prompt again if nothing happens in time.
- Degradation signal. Fall back to a simpler code path automatically when the primary source takes too long.
For scenarios where you need all promises to complete regardless of individual failures, use Promise.allSettled. See Using Promise allSettled for Reliable JS APIs. For running multiple promises in parallel and waiting for all, see Promise All in JavaScript: Complete Guide.
Rune AI
Key Insights
- Promise.race settles with the first promise that settles, whether resolved or rejected.
- Use it to add a timeout to any async operation by racing against a delayed rejection.
- Promise.race does not cancel losing promises. They still run to completion.
- Promise.any is the better choice when you want the first successful result and can tolerate rejections.
- Passing an empty array to Promise.race returns a promise that never settles.
Frequently Asked Questions
Does Promise.race cancel the other promises when one settles?
What is the difference between Promise.race and Promise.any?
What happens if I pass an empty array to Promise.race?
Conclusion
Promise.race is the right tool when you care about which promise finishes first, regardless of whether it succeeds or fails. Use it for timeouts, for picking the fastest of several equivalent data sources, and any scenario where the first response wins.
More in this topic
Using Decorators for Logging in JS Architecture
Learn how JavaScript decorators wrap class methods to add logging without touching the original code, including setup with Babel and how execution order works.
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.