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.

7 min read

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.

javascriptjavascript
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.

texttext
Winner: Fast

The 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.

javascriptjavascript
const result = await Promise.race(iterable);
ParameterDescription
iterableAn iterable (usually an array) of promises
ReturnsA 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.

javascriptjavascript
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.

javascriptjavascript
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.

javascriptjavascript
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.

javascriptjavascript
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.

Promise.race vs Promise.any settlement rules

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.

javascriptjavascript
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.

javascriptjavascript
Promise.any([failingFast, succeedingSlow])
  .then(console.log)
  .catch((err) => console.error("Any:", err.message));
// "Success" (after 1 second)
raceany
Settles withFirst settled, any outcomeFirst resolved, ignores rejections
All rejectRejects with first errorRejects with AggregateError
Empty arrayPending foreverRejects with AggregateError
Use caseTimeouts, first-response winsFastest 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.

javascriptjavascript
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.

javascriptjavascript
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.

javascriptjavascript
// 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.

javascriptjavascript
// 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.

javascriptjavascript
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

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.
RunePowered by Rune AI

Frequently Asked Questions

Does Promise.race cancel the other promises when one settles?

No. Promise.race only cares about the first settled promise. The other promises continue running. To cancel them, you need an AbortController or a custom cancellation mechanism.

What is the difference between Promise.race and Promise.any?

Promise.race settles with the first promise that settles, whether it resolves or rejects. Promise.any waits for the first promise that resolves and ignores rejections unless all promises reject.

What happens if I pass an empty array to Promise.race?

Promise.race([]) returns a promise that stays pending forever. It never resolves or rejects because there are no promises to 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.