All questions

Promise.any

Premium

Promise.any

Implement your own version of Promise.any. You take an iterable of promises (or plain values), kick them all off in parallel, and return a single promise that resolves with the first fulfilment — or, if every input rejects, rejects with an AggregateError carrying every reason in input order. It is the inverse of Promise.all: one success wins, only all-failures lose.

Signature

// Returns a Promise that fulfils with the first input to fulfil,
// or rejects with an AggregateError if every input rejects.
function promiseAny<T>(iterable: Iterable<T | PromiseLike<T>>): Promise<T>;

Examples

// First fulfilment wins — even when an earlier-indexed promise rejects first.
const bad  = Promise.reject('nope');
const slow = new Promise((r) => setTimeout(() => r('slow-win'), 30));
bad.catch(() => {});
promiseAny([bad, slow]).then(console.log); // 'slow-win'
// All reject → AggregateError with errors[i] matching input[i].
promiseAny([Promise.reject('a'), Promise.reject('b')]).catch((err) => {
  console.log(err instanceof AggregateError); // true
  console.log(err.message);                   // 'All promises were rejected'
  console.log(err.errors);                    // ['a', 'b'] — input order, not rejection order
});
// Non-promise values are wrapped and treated as already-fulfilled.
promiseAny([Promise.reject('x'), 42]).then(console.log); // 42
// Empty iterable: there is no fulfilment to wait for, so it rejects immediately.
promiseAny([]).catch((err) => {
  console.log(err instanceof AggregateError); // true
  console.log(err.errors);                    // []
});

Notes

  • First fulfilment, not first settle. Promise.any only cares about fulfilments. A faster rejection does NOT decide the result — it just gets recorded in errors and we keep waiting. This is the opposite of Promise.race.
  • errors is indexed by input position. If the 0th input rejects last and the 2nd rejects first, errors[0] still holds the 0th input's reason. Don't .push() rejections as they arrive.
  • Message is exactly 'All promises were rejected'. Per spec. Tests check the string.
  • Empty iterable rejects with an AggregateError whose errors is []. There is nothing to fulfil, so we fail immediately.
  • Non-promise inputs win immediately. Wrap every item with Promise.resolve so plain values, real promises, and thenables share one code path.
  • A fulfilled value of undefined still wins. Fulfilment is decided by the promise settling fulfilled, not by truthiness — Promise.resolve(undefined) wins over a later rejection.
  • Iterate the input once. It may be a generator. A single for...of is the right shape.
  • Synchronous iterator throws reject the result with an AggregateError rather than throwing to the caller.
  • Always return a real Promise — not the input, not a thenable. Tests check instanceof Promise.
  • Don't use Promise.any to implement promiseAny. That defeats the exercise. You may use new Promise(...), Promise.resolve, and .then.

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium