FossilsðŸ’ŧ CodingImplement Promise.race, Promise.any & Promise.allSettled
ðŸĶ–DinosaurJavaScriptPromisesPolyfill

Implement Promise.race, Promise.any & Promise.allSettled

These three Promise combinators cover the spectrum of concurrent result handling. Implementing them reveals understanding of settlement semantics and AggregateError.

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 rejection

Promise.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

MethodResolves WhenRejects WhenShort-circuits?
Promise.allAll fulfillAny rejectsYes (on first rejection)
Promise.raceFirst settlesFirst settlesYes (first settlement)
Promise.anyFirst fulfillsAll rejectYes (on first fulfillment)
Promise.allSettledAll settleNeverNo

Common Mistakes

  • Using errors.length === pending instead of tracking count properly in Promise.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 AggregateError for Promise.any