Wait for the Fastest JavaScript Promise to Settle with Promise.race()

Marius Schulz
InstructorMarius Schulz

Share this video with your friends

Send Tweet
Published 3 years ago
Updated 8 months ago

The Promise.race() method accepts an array (or any other iterable) of promises as a parameter. It returns a Promise object that is fulfilled or rejected once the first input promise is fulfilled or rejected:

  • As soon as any input promise is fulfilled, the returned Promise object is fulfilled with that value.
  • As soon as any input promise is rejected, the returned Promise object is rejected with that reason.

Promise.race() can be used to race multiple promises against each other and find the first promise to settle.

Instructor: [00:00] Let's take a look at the promise.race method, but before we do that, we need a little bit of set up. I'm going to create a function called resolve after that accepts a number of milliseconds and a value. This function returns a new promise that is resolved with the given value after the given number milliseconds.

[00:21] We can now create two promises for testing. Promise A resolves after one second and promise B resolves after two seconds. Now, we can race those two promises against each other using that promise.race method.

[00:36] Promise.race returns a promise itself, which is set out the same way as the first input promise that settles, but differently as soon as any of the input promises resolves or rejects, the promise return by promise.race is resolved or rejected accordingly.

[00:52] The promise.race method will return the fastest of the input promises if you will. Also, if you pass an empty array, or any other empty attribute, the return promise will forever be pending. Let's attach a fulfillment handler to this promise and see promise.race in action.

[01:13] Let's pull up the console and run this program. After one second, we can see that A is the faster promise. If we go back in here and resolve B after 500 milliseconds, run this again, we can now see that promise B is settled first.

[01:33] All right. Now that, you've seen how promise.race works, let's go ahead and build something slightly more useful. In practice, I don't find myself using promise.race very often, but I have used it to implement a timeout function, so that is exactly what we're going to do.

[01:48] The timeout function will receive a number of milliseconds and a promise that we want to timeout. Within the body of the timeout function, we're going to create a timeout promise.

[01:58] This promise is automatically going to be rejected after the given number of milliseconds. Within the executive function, I'm going to set a timeout and I'm going to reject the promise with an error saying that the operation timed out after this many milliseconds.

[02:18] Notice that we don't need the resolve parameter here, which is why I've named it underscore. This is not special JavaScript syntax. It's just the naming convention. We can now call promise.race to race our actual promise against the timeout promise. That is what we're going to be returning from the function.

[02:36] The idea here is that, if our timeout promise is the promise that settles first, we're going to return a rejected promise from the timeout function. If that's not the case, this promise will settle first, so we didn't run into the timeout.

[02:51] Here is how we would use the timeout function. Let's create a promise again that is resolved after one second. We're going to say it resolve after 1,000 milliseconds with the value A. Then, I'm going to call timeout, pass it 500 milliseconds and our promise.

[03:07] I'm also going to attach fulfillment and rejection handlers, so we can see what is happening. This is the fulfillment handler and for the rejection handler we're going to call console.error, and we're going to log the error message.

[03:21] Let's give this a go, and sure enough we can see the timeout message. Let's now set a five second timeout and run this again. We see the value A log to the console. However, you might have noticed that the process was running for a quite a bit of time.

[03:43] Let's execute the program one more time. At this time, I want to track the number of seconds that the process is running. As you can see, the process was running for just over five seconds. This is because we've been calling setTimeout with a delay of 5,000 milliseconds.

[04:05] Therefore, node doesn't terminate the process until this callback has been run. We can fix this by properly clearing the timeout. We're going to just store the timeout ID in a local variable.

[04:17] Then, we're going to use the finally method to clear the timeout once the promise has been settled. Let's run this one last time. Now, we can see that the process only runs for roughly a second.

Daniel Krejčí
Daniel Krejčí
~ 3 years ago

I am curious. Do you know about any actual use cases for Promise.race? I had no need to use that ever.

Ian Jones
Ian Jones
~ 3 years ago

I am curious. Do you know about any actual use cases for Promise.race? I had no need to use that ever.

A common use case for racing promises is to create a time out on the client. You can send a request and race that request with a timeout of whatever length you give it, passing Promise.reject to the timeout handler.

Marius Schulz
Marius Schulzinstructor
~ 3 years ago

@Daniel: I've almost never used Promise.race() either, but this course wouldn't have been complete without it.

As Ian and I mentioned, racing a promise against a timeout promise is a possible use case. Another scenario might be fetching the same information from two different sources and only waiting for the faster response.

Erkan Buelbuel
Erkan Buelbuel
~ a year ago

I get an error message: TypeError: Promise.race(...).finally is not a function

function resolveAfter(ms, value) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(value);
    }, ms);
  });
}

function timeout(ms, measuringPromise) {
  let timeoutID;
  const timeoutPromise = new Promise((_, reject) => {
    timeoutID = setTimeout(() => {
      reject(Error(`Timed out after ${ms}ms`));
    }, ms);
  });

  return Promise.race([somePromise, timeoutPromise]).finally(() => {
    clearTimeout(timeoutID);
  });
}

const somePromise = resolveAfter(1000, 'Some Content');

timeout(2000, somePromise).then(
  (value) => console.log(value),
  (err) => console.warn(error.message),
);

Node v8.17.0, MacOS Catalina v10.15.6

Marius Schulz
Marius Schulzinstructor
~ a year ago

@Erkan: The Promise.prototype.finally method is only supported as of Node v10.0.0 (see https://node.green/#ES2018-features-Promise-prototype-finally).

Erkan Buelbuel
Erkan Buelbuel
~ a year ago

thank you very much, that must have been the scotch at that time, alread got it,..