Quick! Let's Build Promise.race()
Tags: javascript, webdev, programming
Published: 2024-05-13 15:22:39

Trying to re-create a feature can be a great way to understand it. Let's do that with `Promise.race()`!

## Requirements

[Promise.race](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race) has perhaps the simplest requirements:

>  …takes an iterable of promises as input and returns a single Promise. This returned promise settles with the eventual state of the first promise that settles.

The most common use is with an Array, so we'll start with that assumption, and we can build up the features.

* Takes an array of promises
* Returns one promise
* First settled state is used

### RunJS

I use [RunJS](https://runjs.app) for coding exercises like this. I'm not paid or incentivized to promote it; it's just a good playground for code. All my code samples are written or tested in RunJS.

## Basic Implementation

This is actually _very easy_ to implement because Promises have one very important characteristic: they can only be settled once. So whatever gets there first will win. We just have to iterate over the array.

```javascript
const race = (promises) => new Promise((resolve, reject) => {
  promises.forEach(promise => promise.then(resolve, reject));
});
```

At its _most basic_, that is all `Promise.race()` is doing. A new promise is created. Resolve and reject are passed to each promise in the iterator. First one wins.

Done!

```javascript
const a = Promise.resolve(1);
const b = Promise.resolve(2);

Promise.race([a, b]).then(console.log);
// 1

race([a, b]).then(console.log);
// 1
```

Well, sort-of. There are a number of cases we don't currently cover.

## Non-Promise Entries

```javascript
Promise.race([1, 2]).then(console.log);
// 1

race([1, 2]).then(console.log); 
// Error: 'promise.then is not a function' 
```

While the high-level description on MDN doesn't say so, `Promise.race()` accepts non-promise values. These are converted to resolved promises. This can be very helpful in testing scenarios, as you can build simple synchronous mocks of functions and not worry that the output of functions passed to `Promise.race()` or `Promise.all()` are themselves promises.

So how do we make sure everything becomes a promise? Promises have already solved this issue for us, with `Promise.resolve()`.

### Resolve Everything?

`Promise.resolve()` checks for a promise, and passes it along unchanged. So any existing promise – including rejected ones – will make it through. Non-promises will become resolved promises, which is what we need.

### Non-Promise Support

```javascript
const race = (promises) => new Promise((resolve, reject) => {
  promises.forEach(promise => Promise.resolve(promise)
    .then(resolve, reject));
});
```

Now we can safely pass in non-promise values.

```javascript
Promise.race(['red', 'blue']).then(console.log);
// 'red'

race(['red', 'blue']).then(console.log);
// 'red'
```

## Iterables

Right now our implementation depends on the `.forEach` method, which works for arrays, Sets and Maps, but not for everything. It's a less common scenario, but this is valid:

```javascript
Promise.race('ABC').then(console.log);
// 'A'
```

A string is iterable, but it doesn't have a `forEach` method, so it doesn't work with our implementation.

```javascript
race('ABC').then(console.log);
// Error: 'promises.forEach is not a function' 
```

There are a couple ways we could resolve this... using [`for...of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) to iterate:

```javascript
const race = (promises) => new Promise((resolve, reject) => {
  for (const promise of promises) {
    Promise.resolve(promise).then(resolve, reject);
  }
});
```

Or, if we want to stick with what's familiar, [`Array.from()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) leaves us with an array so we can still use `.forEach()`.

```javascript
const race = (promises) => new Promise((resolve, reject) => {
  Array.from(promises).forEach(promise =>
    Promise.resolve(promise)
    .then(resolve, reject));
});
```

Now we support any iterable.

```javascript
Promise.race('ABC').then(console.log);
// 'A'

race('ABC').then(console.log);
// 'A'
```

## Conclusion

MDN provides a lot of useful information an examples  on, well, _everything_, so be sure to [check out the docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race) if you haven't already.

I hope you've enjoyed this quick trip through `Promise.race()`. Breaking down a piece of functionality to identify the requirements and the features is a useful skill to hone, and re-creating easy-to-verify functionality like this can be a great way to build that skill.

I'd love to hear about the tests and examples you've created/re-created over time!