Promise.allSettled
intermediateImplement allSettled: wait for every promise and report per-promise status objects.
Implement Promise.all in JavaScript: order preservation with a counter, fail-fast rejection, iterable inputs, non-promise values, and the empty-array case.
Implement
promiseAll(iterable): resolve with all results in input order once every promise fulfills; reject immediately with the first rejection.
This is the most-asked combinator implementation, and it's really a test of three specific insights:
results.length to detect completion. Assigning results[5] on an empty array makes length 6 with holes — you need a fulfillment counter.reject on the first rejection wins the settle-once race; later fulfillments call resolve into a dead promise harmlessly.Parallel independent fetches on page load (Promise.all([fetchUser(), fetchCart(), fetchFlags()])), batched writes, and asset preloading. The senior framing: Promise.all is for all-or-nothing dependencies — if the page can render with partial data, allSettled is the right tool, and if the input list is large you need a concurrency pool instead of firing everything at once.
function promiseAll(iterable) {
return new Promise((resolve, reject) => {
const items = Array.from(iterable); // accept ANY iterable, like the spec
const results = new Array(items.length);
let remaining = items.length;
if (remaining === 0) {
resolve([]); // empty input resolves immediately — a classic hang bug
return;
}
items.forEach((item, index) => {
// Promise.resolve handles plain values and foreign thenables alike
Promise.resolve(item).then(
(value) => {
results[index] = value; // input order, not completion order
remaining -= 1;
if (remaining === 0) resolve(results);
},
reject // first rejection wins; settle-once ignores the rest
);
});
});
}const slow = new Promise((res) => setTimeout(() => res("slow"), 300));
const fast = new Promise((res) => setTimeout(() => res("fast"), 100));
await promiseAll([slow, fast, 42]);
// t=0: remaining = 3; all three subscriptions attached
// t≈0: 42 (wrapped by Promise.resolve) fulfills → results[2] = 42, remaining = 2
// t=100: fast → results[1] = "fast", remaining = 1
// t=300: slow → results[0] = "slow", remaining = 0 → resolve
// → ["slow", "fast", 42] ← input order, even though 42 finished firstAnd the fail-fast path:
promiseAll([
new Promise((res) => setTimeout(res, 500, "never seen")),
Promise.reject(new Error("boom")),
]).catch((e) => console.log(e.message)); // "boom" — no waiting for the 500ms oneNote what fail-fast does not do: the slow promise keeps running — Promise.all stops waiting, it doesn't cancel. If the interviewer asks how you'd actually stop the work, the answer is AbortController.
[] synchronously-ish. Forgetting this hangs forever: the forEach never runs, resolve is never called.promiseAll([1, 2, 3]) → [1, 2, 3]. Promise.resolve per item handles it.Promise.all accepts Sets, generators, strings. Array.from up front matches the spec; a hard Array.isArray check that rejects iterables is a spec deviation (worth naming, since many "polyfills" have it).reject.promiseAll([p, p]) gives the value twice at both indexes; nothing special needed, but interviewers use it to check the counter isn't keyed by promise identity.results.push(value) — completion order corrupts the output. The planted bug.if (results.length === items.length) — sparse arrays make length lie; use a counter..then(...).catch(reject) instead of the two-argument then — functionally similar here, but the two-arg form states intent and avoids catching errors thrown by your own fulfillment handler (a subtle self-inflicted bug: if results[index] = value code throws, .catch(reject) would reject the outer promise while the two-arg form lets it surface).Promise.all "runs promises in parallel" — promises are already running when you receive them; all only aggregates. The distinction matters and interviewers listen for it (see N tasks in parallel).Promise.all([
Promise.resolve(1),
new Promise((res) => setTimeout(res, 100, 2)),
Promise.reject(new Error("x")),
]).then((v) => console.log("ok", v))
.catch((e) => console.log("err", e.message));| Resolves when | Rejects when | Empty input | |
|---|---|---|---|
Promise.all | every promise fulfills | first rejection | resolves [] |
allSettled | every promise settles | never | resolves [] |
any | first fulfillment | all reject (AggregateError) | rejects |
race | first settle (either way) | first settle (either way) | pends forever |
Being able to reproduce this table — especially the empty-input column — is a better interview signal than any single implementation.
Promise.race with a timer: race + timeout pattern.remaining -= 1 in different then callbacks can never interleave mid-operation. See the event loop.Implement allSettled: wait for every promise and report per-promise status objects.
Implement a spec-faithful Promise with then chaining, state transitions, and async resolution.
Implement Promise.any with AggregateError semantics: first success wins, all failures reject.
Implement Promise.race and use it for the timeout pattern every senior interview touches.