Implement Promise.race, Promise.any & Promise.allSettled
Interview Question: "Implement Promise.race, Promise.any, and Promise.allSettled from scratch."
Promise.race
Settles with the first promise to settle (fulfill or reject):
function promiseRace(promises) {
return new Promise((resolve, reject) => {
for (const promise of promises) {
Promise.resolve(promise).then(resolve, reject);
}
});
}Key points:
- First settlement wins â subsequent settlements are ignored (a Promise can only settle once)
Promise.resolve(promise)handles non-Promise values in the iterable- Empty iterable: the returned promise never settles (forever pending)
promiseRace([
new Promise(r => setTimeout(() => r('slow'), 200)),
new Promise(r => setTimeout(() => r('fast'), 100)),
]); // 'fast'
promiseRace([
new Promise((_, rej) => setTimeout(() => rej('error'), 50)),
new Promise(r => setTimeout(() => r('ok'), 100)),
]); // rejects with 'error' â first to settle was rejectionPromise.any
Resolves with the first fulfilled promise. Rejects only if all reject:
function promiseAny(promises) {
return new Promise((resolve, reject) => {
const errors = [];
let pending = 0;
for (const promise of promises) {
const index = pending;
pending++;
Promise.resolve(promise).then(
resolve,
(error) => {
errors[index] = error;
if (errors.length === pending) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
}
);
}
if (pending === 0) {
reject(new AggregateError([], 'All promises were rejected'));
}
});
}Key differences from race:
- Rejections are collected, not forwarded immediately
- Only resolves on fulfillment (first one wins)
- Uses
AggregateError(ES2021) to wrap all rejection reasons
promiseAny([
Promise.reject('a'),
Promise.resolve('b'),
Promise.reject('c'),
]); // 'b'
promiseAny([
Promise.reject('x'),
Promise.reject('y'),
]); // AggregateError: All promises were rejected (errors: ['x', 'y'])Promise.allSettled
Waits for all promises to settle, never short-circuits:
function promiseAllSettled(promises) {
return new Promise((resolve) => {
const results = [];
let remaining = 0;
for (const promise of promises) {
const index = remaining;
remaining++;
Promise.resolve(promise).then(
(value) => {
results[index] = { status: 'fulfilled', value };
if (--remaining === 0) resolve(results);
},
(reason) => {
results[index] = { status: 'rejected', reason };
if (--remaining === 0) resolve(results);
}
);
}
if (remaining === 0) resolve([]);
});
}Key points:
- Always resolves (never rejects)
- Result array preserves input order
- Each result is
{ status: 'fulfilled', value }or{ status: 'rejected', reason } - Empty iterable resolves with
[]
promiseAllSettled([
Promise.resolve(1),
Promise.reject('error'),
Promise.resolve(3),
]);
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: 'error' },
// { status: 'fulfilled', value: 3 },
// ]Comparison Table
| Method | Resolves When | Rejects When | Short-circuits? |
|---|---|---|---|
Promise.all | All fulfill | Any rejects | Yes (on first rejection) |
Promise.race | First settles | First settles | Yes (first settlement) |
Promise.any | First fulfills | All reject | Yes (on first fulfillment) |
Promise.allSettled | All settle | Never | No |
Common Mistakes
- Using
errors.length === pendinginstead of tracking count properly inPromise.any - Forgetting to handle empty iterables (race = forever pending, any = reject, allSettled = resolve
[]) - Not wrapping inputs with
Promise.resolve()to handle non-Promise values - Forgetting
AggregateErrorforPromise.any