Promises Deep Dive: Chaining, Errors, all vs allSettled (Interview-Ready)
A practical guide to JavaScript Promises for interviews: states, chaining rules, error propagation, finally, and when to use Promise.all vs allSettled vs race vs any.
Frontend Interview Team
February 08, 2026
Promises are the backbone of modern JS async code. Interviews focus on whether you understand:
- chaining
- error propagation
- concurrency helpers (
all,allSettled,race,any)
30‑second interview answer
A Promise represents a future value with states: pending → fulfilled/rejected. .then() always returns a new promise; returning a value passes it down, returning a promise “flattens” it, and throwing rejects the chain. For concurrency: use Promise.all for fail-fast, allSettled when partial success is OK, race for first-settled, and any for first-fulfilled.
Key points
.thenreturns a new promise.- Errors propagate until the nearest
.catch. finallydoesn’t change the resolved value (unless it throws).- Prefer
Promise.allto run tasks in parallel.
1) Promise states (know the words)
A Promise is always in one of these states:
- pending
- fulfilled (resolved)
- rejected
Once fulfilled/rejected, it’s settled and can’t change.
2) The chaining rule that matters
.then returns a new promise
Promise.resolve(1)
.then((x) => x + 1)
.then((x) => console.log(x)); // 2If you return a value → next .then gets that value.
If you return a promise → next .then waits for it.
Promise.resolve('A')
.then(() => fetch('/api/user'))
.then((res) => res.json())
.then((user) => console.log(user));3) Error propagation (the “bubble” model)
Promise.resolve()
.then(() => {
throw new Error('boom');
})
.then(() => console.log('never'))
.catch((err) => console.log('caught:', err.message));Output: caught: boom
A thrown error inside .then becomes a rejected promise.
Rule: one .catch can catch rejections from any previous step in the chain.
4) .finally behavior
Promise.resolve('ok')
.finally(() => console.log('cleanup'))
.then((v) => console.log(v));finally doesn’t receive the value, and it doesn’t change it (unless it throws).
If finally throws or returns a rejected promise → it overrides the chain result.
5) Concurrency helpers (big interview topic)
Promise.all (fail fast)
Use when:
- you need all results
- and any failure should stop everything
await Promise.all([p1, p2, p3]);If any rejects → Promise.all rejects immediately.
Promise.allSettled (never throws)
Use when:
- you want results for all, even failures
const results = await Promise.allSettled([p1, p2]);
// [{ status: 'fulfilled', value: ... }, { status: 'rejected', reason: ... }]Promise.race (first settled wins)
- resolves/rejects with the first promise to settle.
Use case: timeouts.
const withTimeout = Promise.race([
fetch('/api'),
new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), 2000))
]);Promise.any (first fulfilled wins)
- resolves with the first fulfilled promise
- rejects only if all reject (AggregateError)
Great when you have fallback endpoints.
6) Common pitfalls
- Forgetting to return in
.then
fetch('/api')
.then((res) => { res.json(); }) // missing return!
.then((data) => console.log(data)); // data is undefinedFix:
.then((res) => res.json())- Sequential when you meant parallel
Bad:
const a = await p1;
const b = await p2;Better:
const [a, b] = await Promise.all([p1, p2]);Summary checklist
- I can explain
.thenreturning a new promise. - I can explain how errors propagate.
- I know when to use
allvsallSettled. - I default to
Promise.allfor parallel work.
Summary
.thenreturns a new promise (values pass through, promises are awaited).- Errors bubble down the chain to the first
.catch. - Use
allwhen you need everything and want fail-fast. - Use
allSettledwhen partial success is acceptable. - Use
race/anyfor timeouts/fallbacks.