Topics
v5.0.0
On this page · sections

JavaScript Promises, Async & Concurrency

Can you describe Promise settlement, await desugaring, and when work is sequential vs overlapped?

Promises are the language’s unit of async success and failure. async/await is syntax over the same jobs. Concurrency is about how many operations are in flight, not about extra JS threads.

Combinators (all, race, …) are implemented in Promise Polyfill. This page is the model.

Questions

Promises

  1. Promise States and Settlement
  2. Chaining and Error Propagation

Async

  1. What await Actually Does
  2. Sequential vs Concurrent I/O

Senior-Level Follow-ups

  1. Unhandled Rejections
  2. Concurrency Is Not Parallelism
  3. Why Combinators Do Not Cancel

Promises

1. Promise States and Settlement

Why This Matters

A Promise is not a wrapper you can mutate twice. First settlement wins.

Problem

Name the states and what happens if you resolve then reject.

Example

pending ├── fulfill → fulfilled (value) └── reject → rejected (reason) fulfilled → ❌ rejected → ❌
new Promise((resolve, reject) => { resolve(1); reject(new Error("ignored")); resolve(2); });

The Promise is fulfilled with 1. Later calls are no-ops.

Executor throw rejects:

new Promise(() => { throw new Error("sync"); });

Handlers run as microtasks, even for Promise.resolve(1).then(...).

Interview Answer

“A Promise has three states and settles once. then always schedules a microtask. Errors thrown in the executor reject. I never treat a Promise as a mutable box.”

2. Chaining and Error Propagation

Why This Matters

Missing handlers must propagate. That is how .catch at the end works.

Problem

What does .then return, and how do rejections travel?

Example

Promise.resolve(1) .then((n) => n + 1) .then((n) => { throw new Error("fail"); }) .then((n) => n + 1) .catch((err) => err.message);

The third then is skipped. catch is then(undefined, onRejected).

Returning a Promise from then adopts that Promise. Returning a value fulfills the next Promise. Throwing rejects it.

Interview Answer

“then returns a new Promise. A missing handler forwards the previous fulfillment or rejection. Throw or return a rejected Promise to fail the chain. Return a value or a fulfilled Promise to recover.”

Async

3. What await Actually Does

Why This Matters

async functions always return a Promise. await is not blocking the thread; it is exiting the function until a job runs.

Problem

Desugar await and explain error handling.

Example

async function load() { const value = await fetch("/api"); return value.json(); }

Roughly:

function load() { return Promise.resolve() .then(() => fetch("/api")) .then((value) => value.json()); }
try { await promise; } catch (e) { // rejection }

is .then + .catch with restored stack-looking syntax.

await on a non-Promise wraps with Promise.resolve.

Interview Answer

“An async function returns a Promise. await suspends the async function and continues as a microtask when the operand settles. try/catch around await catches rejections. The thread is not blocked.”

4. Sequential vs Concurrent I/O

Why This Matters

await in a for loop serializes. That is often a bug, sometimes a feature (rate limits, dependent steps).

Problem

When do you await in a loop, and when do you Promise.all?

Example

async function sequential(ids) { const results = []; for (const id of ids) { results.push(await fetchItem(id)); } return results; } async function concurrent(ids) { return Promise.all(ids.map((id) => fetchItem(id))); }
3 × 200ms sequential ≈ 600ms concurrent ≈ 200ms (overlapped I/O, same JS thread)

Unbounded Promise.all on thousands of URLs can exhaust sockets. Cap in-flight work with a scheduler — Task Scheduler.

Interview Answer

“await in a loop is sequential. Mapping to Promises first, then Promise.all, overlaps I/O. I use sequential for dependent steps or throttling, and bounded concurrency for independent fan-out.”

Senior-Level Follow-ups

5. Unhandled Rejections

A rejection with no catch becomes an unhandled rejection. Browsers log it; Node can crash depending on flags.

Always attach a handler, or return the Promise to a caller that will.

Fire-and-forget:

void load().catch(logError);

6. Concurrency Is Not Parallelism

Concurrent: many operations in flight; JS interleaves their callbacks.

Parallel: two cores execute at the same time — Workers, WASM threads, GPU.

Promise.all on fetch is concurrent I/O. It does not parse JSON on two cores.

CPU-heavy work still blocks the main thread until you move it off. See Rendering, Performance & Workers.

7. Why Combinators Do Not Cancel

Promise.all rejects on the first failure. The other fetches keep running.

Timeout via Promise.race stops waiting, not the request.

Cancellation is AbortController. That is a host signal, not a Promise feature.

Final Interview Strategy

1. Three states, first settlement wins 2. then is always a microtask 3. Missing handlers propagate 4. async/await desugars to Promises 5. await in a loop serializes 6. Promise.all overlaps I/O 7. Rejection ≠ cancellation

Senior-level signal: Separate the Promise contract from host I/O. Then choose sequential, concurrent, or bounded based on dependence and load — not based on which syntax looks nicer.

Last updated on