Promise.race() is a static Promise concurrency helper: given an iterable of inputs (usually other Promises), it returns a single Promise that settles—fulfills or rejects—the same way as whichever input settles first in real time, which is not always the left-most slot in your array literal.
Use it for timeouts (race slow work against a timer), fastest replica (two fetch calls to different regions), or first user gesture among several async sources. For “wait for all to succeed,” use Promise.all instead. For “first success only,” compare Promise.any in the FAQ below. To create already-resolved inputs for demos, see Promise.resolve.
Basic Promise.race (fulfill wins)
const promise1 = new Promise((resolve) => {
setTimeout(() => resolve("First promise resolved"), 500);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => reject("Second promise rejected"), 1000);
});
Promise.race([promise1, promise2])
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});You should see one line logging First promise resolved.
Node.js v20.18.2 output after both timers complete; the 500ms branch fulfills first, so the then path runs.
When rejection settles first
const promise1 = new Promise((resolve) => {
setTimeout(() => resolve("First promise resolved"), 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => reject("Second promise rejected"), 500);
});
const timeout = new Promise((resolve, reject) => {
setTimeout(() => reject("Promise timed out"), 750);
});
Promise.race([promise1, promise2, timeout])
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});You should see one line logging Second promise rejected.
The 500ms rejection from promise2 settles before the 750ms timeout and before promise1 resolves, so the catch path runs with that reason. (The original draft text incorrectly blamed the timeout; the timers above match the code.)
Promise.race vs Promise.all: first settled vs all must fulfill
(async () => {
const p1 = Promise.resolve(42);
const p2 = Promise.resolve("Hello World");
const p3 = Promise.reject("Oops");
console.log(await Promise.race([p1, p2, p3]));
try {
await Promise.all([
Promise.resolve(42),
Promise.resolve("Hello World"),
Promise.reject("Oops"),
]);
} catch (e) {
console.log(e);
}
console.log(
JSON.stringify(
await Promise.all([Promise.resolve(42), Promise.resolve("Hello World")]),
),
);
})();You should see 3 lines, in order: 42, Oops, [42,"Hello World"].
Promise.race follows the first settled input (here 42 from an immediately fulfilled Promise.resolve). Promise.all rejects with the first rejection when any input rejects; when all fulfill, you get an array of values.
Timeout pattern (race slow work vs deadline)
function delay(ms, value) {
return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}
function timeout(ms) {
return new Promise((_, reject) =>
setTimeout(() => reject(new Error("timeout after " + ms + "ms")), ms),
);
}
Promise.race([delay(2000, "data"), timeout(100)])
.then((v) => console.log("ok:", v))
.catch((e) => console.log("catch:", e.message));You should see one line logging catch: timeout after 100ms.
The 100ms rejection settles before the 2000ms fulfillment, so the catch branch runs.
Empty iterable stays pending
const r = Promise.race([]);
let settled = false;
r.then(
() => {
settled = true;
},
() => {
settled = true;
},
);
setTimeout(() => {
console.log("after-50ms-settled=" + settled);
}, 50);You should see one line logging after-50ms-settled=false.
Node.js v20.18.2: after 50ms the Promise.race([]) result still had not settled.
Non-Promise values in the iterable
If an element is not a Promise, it is treated as already fulfilled with that value and can win immediately if it appears first:
Promise.race([1, Promise.resolve(2), Promise.resolve(3)]).then((v) =>
console.log("race-mixed:", v),
);You should see one line logging race-mixed: 1.
Summary
Promise.race is the right tool when you care about whichever async branch finishes first, whether it succeeds or fails—promise race / promise.race() traffic usually maps to timeouts, lowest-latency backends, or first response UX. It does not cancel siblings, it does stay pending forever on an empty iterable, and it is not the same as Promise.all (all must fulfill) or Promise.any (first fulfillment only). Pair races with AbortController when you need real cancellation of fetch.
Frequently Asked Questions
1. What does Promise.race do in JavaScript?
2. What is the difference between Promise.race and Promise.all?
3. What is the difference between Promise.race and Promise.any?
4. Does Promise.race cancel the other promises?
5. What happens if you pass an empty array to Promise.race?
6. Does Promise.race use the first promise in the array?
References
Promise.race() - JavaScript | MDN
Promise.any() - JavaScript | MDN
Promise.all() - JavaScript | MDN
