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.
// 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>;
// 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); // []
});
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.'All promises were rejected'. Per spec. Tests check the string.AggregateError whose errors is []. There is nothing to fulfil, so we fail immediately.Promise.resolve so plain values, real promises, and thenables share one code path.undefined still wins. Fulfilment is decided by the promise settling fulfilled, not by truthiness — Promise.resolve(undefined) wins over a later rejection.for...of is the right shape.AggregateError rather than throwing to the caller.Promise — not the input, not a thenable. Tests check instanceof Promise.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.