learn
JavaScript

Promises

States, chaining, error flow, and the four combinators.

A promise is a value with a schedule: pending now, settled exactly once later. Interviewers ask this because chains encode sequencing, error flow and parallelism — the three async skills — in one uniform shape.

Real-life situation

A dashboard needs the user and their posts before first paint. Fired together and joined with Promise.all, one wait covers both, results stay ordered, and a single .catch guards the whole load. Fired in sequence with separate awaits, the same dashboard takes twice as long for no reason.

Visual

Promise states
BrowserAPIRealtimeCacheDatabaseShareddurableephemeral

Try it

Parallel dashboard load
javascriptTab moves to the next control
function getUser() {
return fakeFetch("/users/1", { latencyMs: 5 });
}
function getPosts() {
return fakeFetch("/posts?userId=1", { latencyMs: 5 });
}
Promise.all([getUser(), getPosts()])
.then(function (responses) {
  return Promise.all(
    responses.map(function (r) {
      return r.json();
    }),
  );
})
.then(function (parts) {
  console.log(parts[0].name + ":" + parts[1].length);
})
.catch(function () {
  console.log("dashboard failed");
});
Press Run to see the output.
Expected output
Aarav Sharma:2
Runner helpers

sleep(ms) waits; fakeFetch(url) returns deterministic sample data. Console code runs in a worker with a 5-second limit.

Try changing Promise.all to Promise.allSettled and log the raw parts to see the shape change. Then point one fetch at /missing and watch which branch runs. Say the combinator name before writing it — naming first is what interviewers listen for.

When to use which combinator

all for strict coordination, allSettled for best-effort reports, race for timeouts, any for redundant sources. Default to all; anything else should name its reason in a comment.

Forgotten return — guess first, then reveal.

predict.js
Promise.resolve(2)
.then(function (n) {
  n * 10;
})
.then(function (n) {
  console.log(n);
});

Interview questions

What is a promise, and what are its three states?basic

A promise is a placeholder for a future value. It starts PENDING, then settles exactly once: FULFILLED with a value, or REJECTED with a reason — and never changes again, which is the whole trust upgrade over callbacks. You observe it with .then (success), .catch (failure) and .finally (cleanup either way). One settlement, immutable outcome, chainable handling.

Interviewers also ask

  • Can a promise change state twice?
How does promise chaining work, and what must you return?basic

Each .then returns a NEW promise, so chains pass values down the line — but only if you RETURN from each step. Forgetting return hands the next step undefined (the classic silent bug); returning a promise makes the chain WAIT for it (how sequencing works). Read any chain as an assembly line where each station must pass its product onward explicitly.

Interviewers also ask

  • What does the next then receive if you forget return?
Where do errors go in a promise chain?intermediate

Downward: a rejection skips every success handler until the first .catch (or second then argument), then the chain continues as RESOLVED unless the handler rethrows. So one .catch at the end guards the whole chain, and mid-chain catches mean "recovered, carry on". Unhandled rejections (no catch anywhere) crash modern runtimes — always end chains deliberately.

Interviewers also ask

  • What happens after a catch handles the error?
What is the difference between Promise.all, allSettled, race and any?intermediate

all: every value in order, or the FIRST rejection — the strict coordinator. allSettled: always every outcome (status + value/reason) — the reporter. race: whatever settles FIRST, win or lose — the timeout builder. any: the first FULFILLMENT, ignoring rejections unless all fail (AggregateError) — the redundant-source picker. Name the scenario, then the combinator: dashboard loads are all, best-effort syncs are allSettled.

Interviewers also ask

  • Which one would you use with a timeout?
What does fetch return, and what are the two awaits?basic

fetch returns a promise of a RESPONSE, not data — so you await twice: once for headers (await fetch(url)), once for the body (await res.json()). And fetch only rejects on NETWORK failure; HTTP errors (404, 500) still resolve, so check res.ok yourself. Forgetting either step is the top fetch bug in interviews and production alike.

Interviewers also ask

  • Does fetch reject on a 404?
How do you run promises in sequence versus in parallel?intermediate

Parallel: build ALL the promises first, then await Promise.all — one wait for everything. Sequence: await inside a loop (or reduce a chain), each step waiting for the last — slower, but ordered and side-effect safe. The bug is awaiting inside .map without collecting (looks parallel, runs sequential-ish and drops errors); the fix is map to promises, then one all.

Interviewers also ask

  • What is wrong with awaiting inside map?
What is promise assimilation (thenable handling)?intermediate

When you return a thenable (any object with a .then) from a handler, the chain ADOPTS it — waiting for its outcome instead of wrapping it. This is how foreign promise libraries interoperate and how await accepts any thenable, not just native promises. One level only: assimilation does not recurse into nested thenables beyond the standard procedure, an edge case interviews love.

Interviewers also ask

  • Does await work on non-native thenables?
What does .finally do, and when is it better than duplicating cleanup?basic

.finally runs on settlement either way — success or failure — without receiving the value, perfect for hiding spinners, releasing locks, logging completion. It beats duplicating cleanup in both .then and .catch, and it passes the original outcome through untouched (unless it throws). Think try/finally for chains: the janitor that never steals the result.

Interviewers also ask

  • Does finally see the resolved value?
How do you add a timeout to a promise?intermediate

Race it against a rejecting timer: Promise.race([work, setTimeout(reject, ms)]) — whichever settles first wins, and the loser is ignored (note: NOT cancelled; use AbortController when cancellation matters). Wrap the pattern in a withTimeout(promise, ms) helper so call sites stay clean. Mention the no-cancellation caveat unprompted — it is the expected follow-up.

Interviewers also ask

  • Does the losing side stop running?
What is an unhandled rejection, and why do runtimes crash on it?intermediate

A promise that rejected with no .catch (or await in try) anywhere — an error nobody claimed. Runtimes crash (Node) or warn loudly (browsers) because silent async failures corrupt state invisibly. Prevention: end every chain, await with try/catch, and during development listen for unhandledrejection to catch strays. In interviews, "who catches this rejection?" is always a load-bearing question.

Interviewers also ask

  • How do you detect them during development?

Key takeaways

  • Pending once, then fulfilled or rejected forever; one settlement only.
  • Return from every step or the chain receives undefined.
  • Rejections skip to the nearest catch; unhandled ones crash.

References

Last verified: 2026-10-12.

On this page