Promise.race()
This week I'm looking at the Promise.race()
method, which, like Promise.all()
, Promise.allSettled()
, and Promise.any()
, accepts an iterable of Promise
s. This method, however, returns a Promise
that fulfills or rejects when any single Promise
from the iterable settles, regardless of whether that Promise
fulfilled or rejected. If that Promise
from the iterable fulfills, then the returned Promise
returns the value of that first Promise
; if that Promise
from the iterable rejects, then the returned Promise
returns the reason for that first Promise
's rejection.
I find it helpful to contrast this method against the others I've written about so far since they seem so similar.
Promise.all()
is concerned with all of the Promise
s in its passed iterable fulfilling; this method will reject if any Promise
s in its iterable reject. It fails fast or otherwise returns an array of the values of all fulfilled Promises
.
Promise.allSettled()
is not concerned with fulfillment or rejection of the Promise
s in its passed iterable; it instead provides outcome objects for each Promise
in its iterable, describing whether they fulfilled or rejected. This method does not fail fast or succeed fast, and instead always runs the length of its iterable and returns an array of the outcome objects.
Promise.any()
is concerned with the fastest Promise
of its iterable that fulfills; this method will reject if all Promise
s in its iterable reject or it is passed an empty iterable. It succeeds fast to return the value of the fulfilled Promise
or otherwise returns an AggregateError
that tells you why all of the Promise
s in its iterable failed.
Promise.race()
is concerned with the fastest Promise
of its iterable that settles, i.e., that either fulls or rejects. It succeeds or fails fast, returning either the value of the fulfilled Promise
or the reason that the fastest Promise
failed.
Syntax
Promise.race(iterable);
Parameters
iterable
- An iterable object such as an Array.
Return Value
Promise.race()
returns a pending Promise
that asynchronously yields the value of the first Promise
in the passed iterable to fulfill or reject.
const promise1 = new Promise((resolve, reject) => { setTimeout(() => resolve("first promise done"), 1000);});const promise2 = new Promise((resolve, reject) => { setTimeout(() => resolve("second promise done"), 2000);});const promise3 = new Promise((resolve, reject) => { setTimeout(() => resolve("third promise done"), 500);});const promise4 = new Promise((resolve, reject) => { setTimeout(() => resolve("fourth promise done"), 4000);});
Promise.race([promise1, promise2, promise3, promise4]).then((value) => { console.log(value);});// expected result (after half of a second): "third promise done"
In the code above we have four Promise
s, all of which resolve successfully but at different times. promise3
has the shortest setTimeout()
, so Promise.race()
resolves with the resolved value of promise3
. In this instance Promise.race()
behaves exactly like Promise.any()
.
However, let's take a look at what happens if we make promise3
reject:
const promise1 = new Promise((resolve, reject) => { setTimeout(() => resolve("first promise done"), 1000);});const promise2 = new Promise((resolve, reject) => { setTimeout(() => resolve("second promise done"), 2000);});const promise3 = new Promise((resolve, reject) => { setTimeout(() => reject(new Error("reject!!"), 500));});const promise4 = new Promise((resolve, reject) => { setTimeout(() => resolve("fourth promise done"), 4000);});
Promise.race([promise1, promise2, promise3, promise4]).then( (value) => console.log(value), (error) => console.error(error));// expected result: Error("reject!!")
In this case Promise.race()
returns the Error for promise3
, Error("reject!!")
, since promise3
is the first Promise
in the iterable to settle, even though it rejected. Had we used Promise.any()
instead, it would have moved onto the next Promise
to resolve, which would be promise1
.
If the iterable contains one or more non-Promise
values, then Promise.race()
will resolve to the first of these values found in the iterable.
const promise1 = new Promise((resolve, reject) => { setTimeout(() => resolve("first promise done"), 1000);});const promise2 = "second item done"; // not a Promise!const promise3 = new Promise((resolve, reject) => { setTimeout(() => resolve("third promise done"), 2000);});
Promise.any([promise1, promise2, promise3]).then((value) => { console.log(value);});// expected result (immediately):// "second item done"
Similarly, if the iterable contains one or more already-settled Promise
s, then Promise.race()
will resolve to the first already-settled Promise
found in the iterable.
const promise1 = new Promise((resolve, reject) => { setTimeout(() => resolve("first promise done"), 1000);});const promise2 = Promise.resolve("already resolved promise");const promise3 = new Promise((resolve, reject) => { setTimeout(() => resolve("third promise done"), 2000);});
Promise.race([promise1, promise2, promise3]).then((value) => { console.log(value);});// expected result (immediately):// "already resolved promise"
Finally, if the iterable is empty, then the Promise
that Promise.race()
returns will be forever pending.
const uhoh = Promise.race([]).then((value) => { console.log(value);});
console.log(uhoh);// expected result: Promise { <pending> }
I admit that this last note is a puzzling one to me, considering that Promise.all()
fulfills synchronously with an empty iterable and Promise.any()
rejects with an empty iterable. There is an interesting discussion I found StackOverflow about this behavior from this particular method that you can read here.
From MDN: The Promise.race()
method returns a Promise
that fulfills or rejects as soon as one of the Promise
s in an iterable fulfills or rejects, with the value or reason from that Promise
.
See more examples and further documentation here.