Promise.any
intermediateImplement Promise.any with AggregateError semantics: first success wins, all failures reject.
Implement Promise.allSettled in JavaScript: status objects for every outcome, why it never rejects, and the reflect-pattern one-liner built on Promise.all.
Implement
allSettled(iterable): wait for every promise to settle and resolve with an array of{ status: "fulfilled", value }/{ status: "rejected", reason }objects, in input order. The returned promise never rejects.
Usually asked as the follow-up to Promise.all — and the smart move is to say it's a delta, then show the delta: same counter, same index-addressed results, but rejections are captured as data instead of propagated.
Anywhere partial failure is acceptable: a dashboard loading 6 widgets where 5 should render if one API is down; batch jobs reporting per-item outcomes; fire-N-notifications-and-log-failures. The design sentence interviewers reward: all treats rejection as control flow; allSettled treats it as data.
function allSettled(iterable) {
return new Promise((resolve) => { // note: no reject parameter needed
const items = Array.from(iterable);
const results = new Array(items.length);
let remaining = items.length;
if (remaining === 0) {
resolve([]);
return;
}
items.forEach((item, index) => {
Promise.resolve(item).then(
(value) => { results[index] = { status: "fulfilled", value }; },
(reason) => { results[index] = { status: "rejected", reason }; }
).then(() => {
remaining -= 1; // one counter decrement for either outcome
if (remaining === 0) resolve(results);
});
});
});
}The chained second .then runs after either branch recorded its result — one decrement site instead of two copies, which is exactly the kind of duplication interviewers nudge you to factor out.
If you've already implemented Promise.all, allSettled is a mapping:
const reflect = (p) =>
Promise.resolve(p).then(
(value) => ({ status: "fulfilled", value }),
(reason) => ({ status: "rejected", reason })
);
const allSettled = (items) => Promise.all(Array.from(items).map(reflect));reflect converts any promise into one that always fulfills — with its outcome as data. Once nothing can reject, Promise.all's fail-fast has nothing to fail on. Offering both versions — "here's the from-scratch one; here's how I'd actually write it" — is a strong senior move.
await allSettled([
Promise.resolve(1),
Promise.reject(new Error("nope")),
"plain value",
]);
// [
// { status: "fulfilled", value: 1 },
// { status: "rejected", reason: Error("nope") },
// { status: "fulfilled", value: "plain value" },
// ]
// — and the outer promise FULFILLED despite the rejection in the middlevalue key on fulfillment, reason key on rejection, never both. Downstream code does r.status === "fulfilled" ? r.value : fallback; wrong keys break it silently.[], same as all.Promise.all; the entire point here is that rejection is data.remaining decrements drifting apart during edits (the factored .then above avoids it).{ status, value, reason } with undefined filler keys — close, but observably different from spec via "reason" in result..finally for the decrement but recording results in earlier links sloppily — fine when careful, but the two-arg then + chained decrement is harder to get wrong.| Resolves when | Rejects when | Empty input | |
|---|---|---|---|
all | every fulfills | first rejection | resolves [] |
allSettled | every settles | never | resolves [] |
any | first fulfillment | all reject | rejects |
race | first settle | first settle | pends forever |
allSettled the wrong choice?" — when downstream work needs every result: silently rendering with holes can be worse than failing loudly. Partial tolerance is a product decision, not a default.status: { status: "fulfilled"; value: T } | { status: "rejected"; reason: unknown } — narrowing via if (r.status === "fulfilled") is the idiomatic consumer pattern.allSettled standardized what everyone was hand-rolling. Knowing the direction of that flow is good historical taste.Implement Promise.any with AggregateError semantics: first success wins, all failures reject.
Implement Promise.all: aggregate results in order, fail fast on the first rejection.
Implement Promise.race and use it for the timeout pattern every senior interview touches.
Implement a spec-faithful Promise with then chaining, state transitions, and async resolution.