jsguides

Promise.race()

Syntax

Promise.race(iterable)

Parameters

ParameterTypeDescription
iterableIterableAn iterable (such as an Array) containing promises or other values to race.

Return Value

A Promise that resolves or rejects as soon as one of the promises in the iterable resolves or rejects, adopting the outcome of the first settled promise.

Description

Promise.race() returns a promise that settles as soon as any one of the promises in the iterable settles. The winning promise determines the outcome—whether it resolves or rejects, the returned promise adopts the same state.

This method is useful for implementing timeouts. By racing a potentially long-running operation against a timeout promise, you can reject if the operation takes too long:

function fetchWithTimeout(url, timeoutMs) {
  return Promise.race([
    fetch(url),
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('Timeout')), timeoutMs)
    )
  ]);
}

If the iterable contains non-promise values, they are treated as immediately resolved promises.

Examples

The timeout example above demonstrates the most common practical use of Promise.race() — setting a deadline for async work. The next example strips the pattern down to its essentials, racing two setTimeout-based promises directly. Watching the output confirms that the faster timer wins, which is the fundamental behaviour you rely on when building any timeout wrapper.

Basic race between two promises

const fast = new Promise(resolve => setTimeout(() => resolve('fast'), 100));
const slow = new Promise(resolve => setTimeout(() => resolve('slow'), 500));

Promise.race([fast, slow])
  .then(console.log); // Output: 'fast' (resolves after 100ms)

The basic race shows the principle clearly — the 100ms timer beats the 500ms one. The next block turns that same pattern into a reusable fetchWithTimeout helper, where a slow or unresponsive API call loses to a timer that rejects after the specified deadline. This is the version you would actually ship in production code.

Implementing a timeout

const fetchWithTimeout = (url, ms) => {
  const timeout = new Promise((_, reject) => 
    setTimeout(() => reject(new Error('Request timed out')), ms)
  );
  
  return Promise.race([fetch(url), timeout]);
};

fetchWithTimeout('https://api.example.com/data', 3000)
  .then(response => console.log(response))
  .catch(err => console.error(err));

The timeout wrapper races an HTTP request against a timer, so if the API takes longer than the deadline the promise rejects. The next example shows an important edge case: values that are not promises (or already-resolved promises) settle immediately on the next microtask, so they win against any timer-based delay. This matters when you mix cached results with live fetches in a race.

Race with immediate values

Promise.race([Promise.resolve('instant'), new Promise(r => setTimeout(() => r('delayed'), 1000))])
  .then(console.log); // Output: 'instant'

race() does not cancel the losing promises

When one promise wins the race, the others continue running — they are not cancelled. There is no way to cancel a plain Promise. If the losing promises have side effects (network requests, timers, writes), those effects still happen. For cancellable operations, combine race() with an AbortController and pass the signal to each operation that supports it.

race() vs any()

Promise.race() settles with the first settled promise — whether it resolves or rejects. Promise.any() ignores rejections and settles with the first resolved promise; it only rejects if all promises reject. Use race() when a rejection means “stop immediately” (timeout, first error wins). Use any() when you want the first success and can tolerate some failures.

race() with an already-resolved promise

If the iterable contains a non-Promise value or an already-resolved Promise, race() resolves on the next microtask tick. This is useful for setting a minimum wait time:

// Resolve immediately if data is already available, otherwise wait
const result = await Promise.race([
  cachedData ? Promise.resolve(cachedData) : fetchFresh(),
  new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 5000))
]);

race() with an empty iterable

Passing an empty array to Promise.race([]) returns a Promise that never settles — it stays pending forever. Unlike Promise.all([]), which resolves immediately with an empty array, Promise.race([]) has no winner and therefore no result. Avoid passing an empty iterable unless a permanently pending Promise is intentional.

See Also