Promise Combinators (all, allSettled, race, any)
Key Learning Objectives
Master the four ECMAScript Promise combinators: Promise.all, Promise.allSettled, Promise.race, and Promise.any.
Understand fulfillment and rejection criteria, short-circuit semantics, and input vs completion ordering.
Learn edge-case handling for empty iterables ([]), including the non-settling Promise.race([]) and AggregateError with Promise.any([]).
Compare Promise.all fail-fast behavior with Promise.allSettled resiliency in distributed microservice architectures.
Write polyfills for Promise.all and Promise.allSettled under interview conditions.
The Interview Problem
What is logged to the console when the four Promise combinators execute with a rejected and a resolved promise, and how do their fulfillment and rejection rules diverge?
1async function runCombinators() {2 const p1 = Promise.reject('E1');3 const p2 = Promise.resolve('OK');45 const res1 = await Promise.all([p1, p2]).catch((e) => `all:${e}`);6 const res2 = await Promise.any([p1, p2]).then((v) => `any:${v}`);7 const res3 = await Promise.race([p1, p2]).catch((e) => `race:${e}`);8 const res4 = await Promise.allSettled([p1, p2]).then((s) =>9 `settled:${s.map((x) => x.status).join(',')}`10 );1112 console.log([res1, res2, res3, res4].join(' '));13}1415runCombinators();
Predict Console Output
Select the option that matches what standard ECMAScript prints to the console:
all:E1 any:OK race:E1 settled:rejected,fulfilled
all:OK any:OK race:OK settled:fulfilled,fulfilled
all:E1 any:E1 race:E1 settled:rejected,rejected
all:E1 any:OK race:OK settled:rejected,fulfilled
V8 Engine Execution Trace
Step 1 of 6 (Line 2)Two settled promises are initialized: p1 is rejected with 'E1', p2 is fulfilled with 'OK'.
Deep Technical Breakdown
Comprehensive Comparison of the 4 Promise Combinators
JavaScript provides four distinct static methods on Promise for coordinating concurrent operations:
| Combinator | Added | Fulfills When | Rejects When | Short-Circuits? |
|---|---|---|---|---|
Promise.all | ES2015 | All promises fulfill (returns array in input order) | Any single promise rejects | Yes, on 1st rejection |
Promise.allSettled | ES2020 | All promises settle (returns { status, value/reason }[]) | Never rejects | No (waits for all) |
Promise.race | ES2015 | First promise settles (fulfills if 1st fulfills) | First promise settles (rejects if 1st rejects) | Yes, on 1st settlement |
Promise.any | ES2021 | First promise fulfills | All promises reject (with AggregateError) | Yes, on 1st fulfillment |
The Empty Iterable ([]) Trap
Interviewers love testing empty array inputs because the four methods exhibit sharply contrasting behaviors:
Promise.all([]): Immediately fulfills with[](synchronous tick microtask).Promise.allSettled([]): Immediately fulfills with[].Promise.race([]): Remains pending forever! Since there are no promises to settle, the race has no contenders and never completes.Promise.any([]): Immediately rejects withAggregateError: All promises were rejected.
Polyfill Implementation: Promise.all
function promiseAll(iterable) {
return new Promise((resolve, reject) => {
const items = Array.from(iterable);
if (items.length === 0) return resolve([]);
const results = new Array(items.length);
let completed = 0;
items.forEach((item, index) => {
// Wrap item with Promise.resolve to handle non-promise primitives
Promise.resolve(item).then(
(value) => {
// Store at original index to preserve input order!
results[index] = value;
completed++;
if (completed === items.length) {
resolve(results);
}
},
(err) => {
// Fast fail on first rejection
reject(err);
}
);
});
});
}When to Use Which Combinator
- Use
Promise.allwhen operations depend on one another (e.g. fetching user data AND authorization token before rendering). - Use
Promise.allSettledwhen tasks are independent and partial success is acceptable (e.g. bulk email sending or dashboard widgets where failed widgets render an error state without breaking the page). - Use
Promise.racefor timeouts (e.g.Promise.race([fetchData(), timeout(5000)])). - Use
Promise.anyfor redundant mirrors or CDNs (fetch from 3 CDNs simultaneously, use whichever responds first successfully).
Common Traps & Mistakes
Assuming `Promise.all` results array is sorted in the order promises finish. The results array strictly preserves input order, regardless of which network request completed first.
Using `Promise.race([])` without recognizing that it creates an un-settled pending promise that never resolves or rejects.
Forgetting to wrap items in `Promise.resolve(item)` in combinator polyfills, causing non-promise values (numbers, strings) to throw errors.
Expecting `Promise.any` to reject when the first promise fails. It ignores rejections until every single promise has failed.
FAANG Follow-Up Probes
Probe #1
How would you implement a concurrency limiter (p-limit) that executes at most N promises concurrently using Promise combinators?
Probe #2
Why does Promise.race([p1, timeout]) fail to cancel the underlying HTTP request in p1, and how does AbortController solve this?
Probe #3
What is the structure of AggregateError thrown by Promise.any, and how do you access individual rejection reasons (`err.errors`)?
