Implementing Promise.race in JavaScript


Let's dive into implementing Promise.race, another useful method in JavaScript's Promise API. This method takes an iterable of promises and returns a promise that resolves or rejects as soon as one of the promises in the iterable resolves or rejects, with the value or reason from that promise.


What is Promise.race?

Promise.race is designed to return a promise that resolves or rejects as soon as one of the provided promises resolves or rejects. This is useful when you need to proceed with the first available result, whether it is a success or a failure.

Real Interview Insights

Interviewers might ask you to:

  • Implement Promise.race from scratch.
  • Explain the behavior of Promise.race with various combinations of promises.
  • Discuss how Promise.race compares to other promise methods like Promise.all and Promise.any.

Implementing Promise.race

Here’s a basic implementation of Promise.race:

function promiseRace(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      throw new TypeError('Input must be an array');
    }
 
    promises.forEach(promise => {
      Promise.resolve(promise)
        .then(resolve)
        .catch(reject);
    });
  });
}
Explanation:
  • Input Validation: Ensure the input is an array. If not, throw a TypeError.
  • Promise Processing: Use Promise.resolve to handle both promise and non-promise values. Resolve or reject the returned promise as soon as the first promise resolves or rejects.

Practical Example

Consider an example with multiple promises:

const promise1 = new Promise((resolve) => setTimeout(resolve, 500, 'First'));
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'Second'));
const promise3 = new Promise((resolve, reject) => setTimeout(reject, 200, 'Third'));
 
promiseRace([promise1, promise2, promise3])
  .then(result => console.log(result)) // Output: 'Second'
  .catch(error => console.error(error));

In this example:

  • The promiseRace function returns the result of promise2 as it is the first promise to resolve, ignoring the results of promise1 and promise3.

Advanced Use Case: Handling Empty Arrays

To handle empty arrays gracefully, ensure your implementation checks for this case and resolves with an appropriate value:

function promiseRace(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      throw new TypeError('Input must be an array');
    }
 
    if (promises.length === 0) {
      return resolve();
    }
 
    promises.forEach(promise => {
      Promise.resolve(promise)
        .then(resolve)
        .catch(reject);
    });
  });
}

Performance Considerations

When implementing Promise.race, consider:

  • Early Resolution: The function resolves or rejects as soon as one of the promises settles, which is efficient.
  • Error Handling: Be aware of how errors are propagated in the case of multiple rejections.

Coding Challenge: Enhancing Promise.race with Timeout

Challenge: Enhance the promiseRace function to include a timeout mechanism, allowing it to reject if no promises settle within a specified time limit.

function promiseRace(promises, timeout = 5000) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      throw new TypeError('Input must be an array');
    }
 
    if (promises.length === 0) {
      return resolve();
    }
 
    let timeoutId = setTimeout(() => {
      reject(new Error('Operation timed out'));
    }, timeout);
 
    promises.forEach(promise => {
      Promise.resolve(promise)
        .then(result => {
          clearTimeout(timeoutId);
          resolve(result);
        })
        .catch(error => {
          clearTimeout(timeoutId);
          reject(error);
        });
    });
  });
}
 
// Example usage with timeout
const promise1 = new Promise((resolve) => setTimeout(resolve, 500, 'First'));
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'Second'));
const promise3 = new Promise((resolve, reject) => setTimeout(reject, 200, 'Third'));
 
promiseRace([promise1, promise2, promise3], 300)
  .then(result => console.log(result)) // Output: 'Second'
  .catch(error => console.error(error)); // If no promise resolves/rejects within 300ms

In this challenge:

  • Add a timeout to reject the promise if no promises settle within the specified time.