JavaScript > Asynchronous JavaScript > Promises > Promise.race()

Promise.race() - Racing to Completion

Promise.race() allows you to execute multiple Promises concurrently and resolves with the value of the first Promise that settles (either resolves or rejects). This snippet demonstrates how it works and when it's useful.

Basic Usage

This code creates two Promises, promise1 and promise2, which resolve after different time intervals. Promise.race() is used to execute both Promises concurrently. The .then() block receives the value of the first Promise that resolves, which in this case is promise2 because it resolves after only 100ms.

const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'Promise 1 resolved');
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'Promise 2 resolved');
});

Promise.race([promise1, promise2])
  .then((value) => {
    console.log(value); // Expected output: 'Promise 2 resolved'
  })
  .catch((error) => {
    console.error(error);
  });

Concepts Behind Promise.race()

Promise.race() takes an array of Promises as input. It returns a new Promise that mirrors the resolution or rejection of the first Promise in the array that settles. It's essentially a race condition where the winner (the first settling Promise) determines the outcome.

Rejection Handling

If the first Promise to settle is rejected, the Promise returned by Promise.race() will also be rejected. The .catch() block is used to handle the rejection. In this case, promise2 rejects before promise1 resolves, so the .catch() block is executed.

const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'Promise 1 resolved');
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(reject, 100, 'Promise 2 rejected');
});

Promise.race([promise1, promise2])
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.error(error); // Expected output: 'Promise 2 rejected'
  });

Real-Life Use Case: Timeout

A common use case for Promise.race() is to implement a timeout. The withTimeout function takes a Promise and a timeout value as arguments. It creates a new Promise that rejects after the specified timeout. Promise.race() is used to execute the original Promise and the timeout Promise concurrently. If the original Promise takes longer than the timeout, the timeout Promise will reject first, causing the .catch() block to be executed.

function withTimeout(promise, timeout) {
  const timeoutPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('Timeout'));
    }, timeout);
  });

  return Promise.race([promise, timeoutPromise]);
}

const myPromise = new Promise((resolve, reject) => {
  setTimeout(resolve, 2000, 'Operation completed');
});

withTimeout(myPromise, 1000)
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.error(error); // Expected output: Error: Timeout
  });

Best Practices

  • Always handle rejections in your Promises using .catch() or .finally().
  • Use descriptive error messages for rejections.
  • Consider using a timeout to prevent Promises from hanging indefinitely.

Interview Tip

Be prepared to explain the difference between Promise.all(), Promise.allSettled(), Promise.any() and Promise.race(). Promise.race() settles as soon as one of the promises settles (resolve or reject), while Promise.all() waits for all to resolve (or rejects immediately if any rejects), Promise.allSettled() waits for all promises to settle regardless of outcome, and Promise.any() settles as soon as one promise resolves, rejecting only if all promises reject.

When to Use Promise.race()

Use Promise.race() when you need to execute multiple asynchronous operations concurrently and only care about the result of the first one that completes, regardless of whether it resolves or rejects. The timeout scenario is a perfect example.

Memory Footprint

Promise.race() itself doesn't have a significant memory footprint. However, the Promises it's racing *do*. Keep in mind that even if one Promise wins the race and settles, the *other* Promises may still be executing in the background if they haven't completed yet. If those other promises have significant memory usage, consider strategies for canceling them or mitigating their impact after the race has concluded.

Alternatives

If you need to wait for *all* Promises to resolve, use Promise.all(). If you need to know the outcome of all Promises, regardless of whether they resolve or reject, use Promise.allSettled(). If you want the first Promise to resolve, but reject only if all reject, use Promise.any(). There isn't a direct alternative that provides the exact same functionality as Promise.race(), because its main feature is 'first to settle'. Manually implementing similar logic would involve significantly more code.

Pros

  • Efficiently handles scenarios where only the first result matters.
  • Simple and concise syntax.
  • Useful for implementing timeouts and other race conditions.

Cons

  • The losing Promises may continue to execute in the background, potentially wasting resources if they are not handled properly.
  • Requires careful error handling to prevent unhandled rejections.

FAQ

  • What happens if the array passed to Promise.race() is empty?

    If the array passed to Promise.race() is empty, the Promise returned by Promise.race() will remain pending forever.
  • Does Promise.race() guarantee that the losing Promises will be canceled?

    No, Promise.race() does not automatically cancel the losing Promises. You need to implement custom logic to handle the cancellation of the losing Promises if necessary.