Promise.race() implementation

What is Promise.race()

Promise.race() is a static promise concurrency method that takes an iterable of elements (usually Promises) as an input, and returns a single Promise.

This returned promise will resolve when any of the input's promises fulfills with the first fulfillment value. It rejects when any of the input's promises rejects with its rejection reason.

If the iterable passed is empty, the promise returned will be forever pending

example -

const p1 = Promise.resolve(2);
const p2 = 5;
const p3 = new Promise((resolve,reject) => {
  setTimeout(() => resolve("response after 2s"), 2000)
})
 
 
const result = await Promise.race([p1,p2, p3]); // 5

Implementation of our own promiseRace method -

  • We'll return a new Promise with its settled value as soon as any of the promise is settled.

edge cases

  • Empty input array. A forever-pending promise should be returned.
  • If the array contains non-Promise values, Promise.race() will resolve to the first of these values found in the iterable.

implementation of promiseRace function -

export default function promiseRace(iterable) {
	return new Promise((resolve, reject) => {
		if (iterable.length === 0) return;
		iterable.forEach(it => Promise.resolve(it).then(res, rej));
	});
}
  • It's important to reject() rejected promises in the .then() call (via the second callback parameter) and not within catch(). The approach below looks similar but doesn't work for cases where the iterable contains both immediately resolved and rejected promises (e.g. [Promise.reject(42), Promise.resolve(2)]).

  • .catch() is scheduled, and does not run immediately after .then(). For immediately settled promises, then() run before any .catch(), hence the overall Promise is fulfilled with 2 instead of rejected with 42.

export default function promiseRace(iterable) {
	return new Promise((resolve, reject) => {
		if (iterable.length === 0) return;
    // wrong aaproach
    // highlight-start
		iterable.forEach(it => Promise.resolve(it).then(res).catch(rej));
    // highlight-end
	});
}

Usage example of our promiseRace function

  • using promiseRace with all resolved promises
const p1 = new Promise((resolve,reject) => {
  setTimeout(() => resolve("response after 1s"), 1000)
});
const p2 = 5;
const p3 = new Promise((resolve,reject) => {
  setTimeout(() => resolve("response after 2s"), 2000)
})
 
await promiseRace([p1, p2, p3]); // 5
  • using promiseAny with rejected promise
const p1 = new Promise((resolve) => {
setTimeout(() => {
  resolve(5);
}, 400);
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('Err!');
  }, 100);
});
 
try {
  await promiseRace([p1, p2]);
} catch (err) {
  console.log(err); // 'Err!'
}

Usage

Promise.race() method is typically used when we want the first async task to complete, but do not care about its eventual state (i.e. it can either succeed or fail).

We can use Promise.race to implement request timeout for an api call

const data = Promise.race([
  fetch("/api"),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("request timed out")), 5000));
])
.then(res => res.json())
.catch(err => alert(err));
  • Insert a dummy promise that will be rejected after a certain time. So if our api doesn't resolves before time, then our timeout promise will get fulfilled.
  • If the data promise fulfills, it will contain the data fetched from /api otherwise, it will reject if fetch remains pending for 5 seconds and loses the race with the setTimeout timer.

Info

You can read more about Promise.race() on mdn docs here