How does Promise.any decide what to settle with, and what exactly is the rejection value when every promise you passed it fails?
answer
- first success, not first settle
- rejections are collected, not fatal
- rejects only when all reject
- AggregateError.errors in input order
- empty input rejects at once
basics
~20 sPromise.any fulfils with the value of the first input promise to fulfil, ignoring rejections along the way. Only if every input rejects does it reject, with an AggregateError whose errors property holds every reason in input order.
solid answer
~40 s`Promise.any` is the first-*success* combinator, added in ES2021. It subscribes to every entry and fulfils as soon as any one of them fulfils, discarding rejections that arrive before that. Failures are not thrown away, though: they are collected, and if all inputs reject the returned promise rejects with an `AggregateError` — a real `Error` subclass whose `errors` property is an array of the individual reasons in **input order**, not completion order. The engine also supplies a message such as "All promises were rejected". Two edge cases matter: `Promise.any([])` rejects immediately with an `AggregateError` carrying an empty `errors` array, and, as with any combinator, the losing promises are not cancelled — they run to completion with their results discarded.
code
javascript · 11 linesconst fail = (msg) => Promise.reject(new Error(msg));
Promise.any([fail('a'), Promise.resolve('ok'), fail('c')])
.then((v) => console.log('fulfilled:', v)); // fulfilled: ok
Promise.any([fail('a'), fail('b')]).catch((err) => {
console.log(err instanceof AggregateError); // true
console.log(err.errors.map((e) => e.message)); // [ 'a', 'b' ]
});
Promise.any([]).catch((err) => console.log(err.errors)); // []go deeper
Know that Promise.any gives you the first promise that succeeds and only fails if every one of them fails. Remember the failure value is a single AggregateError, not an array.
Explain the collection mechanics: rejections are accumulated until the count is exhausted, then wrapped in an AggregateError whose errors array is positional with the input. Know that the empty iterable rejects immediately.
Show that you have used it in a fan-out and know its costs: failures before the first success are invisible unless you instrument each source, and every loser keeps consuming quota and bandwidth with no way to stop it.
Own the question of whether redundant fan-out is the right shape at all — the multiplied load on every dependency, the cost per extra source, and when hedging one delayed backup beats firing all sources at once.
## What the combinator promises `Promise.any(iterable)` — standardised in ES2021 — returns a promise that **fulfils with the first fulfilment** among the inputs. Rejections that land before that first fulfilment are not fatal; they are recorded and the combinator keeps waiting. Only when every input has rejected does it give up. This is the piece that distinguishes it from the first-past-the-post behaviour of `Promise.race`, which adopts whichever outcome arrives first, success or failure. `any` is what you want for redundancy: three mirrors of the same asset, two regional endpoints, a cache lookup alongside an origin fetch. One dead source should not sink the operation. ```javascript const fail = (msg) => Promise.reject(new Error(msg)); Promise.any([fail('mirror-1'), Promise.resolve('payload'), fail('mirror-3')]) .then(console.log); // payload ``` ## The all-failed case: AggregateError When the last input rejects, `any` has N reasons and must reject with exactly one value. Wrapping them in an array would be a poor error — `catch` blocks expect an `Error`. So the language introduced `AggregateError`, an `Error` subclass whose job is to carry several underlying failures: ```javascript Promise.any([fail('a'), fail('b')]).catch((err) => { console.log(err instanceof AggregateError); // true console.log(err instanceof Error); // true console.log(err.errors.map((e) => e.message)); // [ 'a', 'b' ] }); ``` Three properties of that object are worth knowing precisely: - **`errors` is an array of the reasons, in input order.** Index `i` of `errors` corresponds to entry `i` of the iterable, regardless of which failed first in time. That correspondence is what lets you report "mirror-3 returned 503" rather than "something failed". - **`errors` is non-enumerable.** It does not appear in an object spread or in `Object.keys`, so a logger that shallow-copies the error can drop it. Read `err.errors` explicitly when logging. - **`AggregateError` is directly constructible** — `new AggregateError([e1, e2], 'all sources failed')` — which is how you would rethrow the same shape from your own fan-out helper. The message is supplied by the engine (commonly "All promises were rejected"); the part you should rely on and assert against in tests is `errors`, not the wording. ## The empty-iterable asymmetry `Promise.any([])` rejects **immediately** with an `AggregateError` whose `errors` array is empty. That is the opposite of `Promise.race([])`, which stays pending forever. The reasoning is consistent with each combinator's contract: `any` must produce a fulfilment from some input, and with no inputs it can never do so, so the failure is knowable up front; `race` has no outcome to adopt and no failure condition to report, so it simply never settles. Interviewers like this pair because it separates people who memorised the happy path from people who read the semantics. ## What it does not do `any` gives you no cancellation. When the first fulfilment arrives, the still-pending inputs keep running: the other two mirror requests complete, consume bandwidth and quota, and their responses are discarded. If those calls have side effects — writes, charges, rate-limit consumption — fan-out with `any` multiplies them by the number of sources. Nothing in the combinator can stop that; a promise is a handle on a result, not on the work behind it, so stopping requires an abort mechanism on the underlying operation. It also gives you no partial information on the way to a success. If two of three sources fail but the third succeeds, the two failures are silently dropped — they exist only in the `AggregateError` that is never created. If you need to alert on degraded mirrors, attach your own logging to each source before handing it to `any`: ```javascript const watched = sources.map((p, i) => p.catch((e) => { log.warn({ source: i, err: e }); throw e; }), ); const payload = await Promise.any(watched); ``` Rethrowing inside the `catch` is essential — swallow the error and that source now looks like a fulfilment, so `any` would resolve with `undefined` from the first failure. ## Choosing between the two Use `race` when the earliest settlement *is* the answer you want — a deadline, a cutoff, a first-signal-wins gate. Use `any` when you want the earliest **usable** answer and treat individual failures as noise. Reaching for `race` on a redundancy problem is the classic mistake: the fastest source to fail wins, and the whole operation rejects while healthy sources are still in flight.
- If two of three sources fail but the third succeeds, how do you find out about the two failures?You do not get them from `Promise.any` — the `AggregateError` is only created when every input rejects, so failures on the way to a success are dropped. Attach a `.catch` to each source that logs and then rethrows before passing it to `any`. Rethrowing matters: swallowing the error turns that source into a fulfilment with `undefined`, which `any` would happily return.
- Why does Promise.any([]) reject immediately while Promise.race([]) never settles?Each follows its own contract. `any` must fulfil from some input; with no inputs that is impossible, so the failure is known up front and it rejects at once with an `AggregateError` holding an empty `errors` array. `race` has no outcome to adopt and no failure condition of its own, so nothing can ever settle it and it stays pending forever.
- How would you preserve which source produced each failure when reporting an AggregateError?`errors` is already positional — index `i` matches input `i` — so keep the array you passed in and zip the two together when reporting. If the sources are built dynamically, wrap each rejection reason with its label before it reaches the combinator, for example by catching and rethrowing an error whose `cause` or message names the source.
saying these in an interview costs you the question
- Says any rejects as soon as one input fails
- Expects the rejection reason to be a plain array of errors
- Thinks AggregateError has no errors property to read
- Claims Promise.any([]) stays pending like race
- Assumes losing sources stop once the first one fulfils