Skip to content
RungsySign in

Callbacks & Promises

How JavaScript represents 'not yet' — and why a Promise is not a thread.

45 minDifficulty 2/5language · asyncReviewedRead and accepted by a person.

Before this

Why this exists

Before Promises, asynchronous code nested. Three dependent operations meant three levels of indentation, error handling duplicated at every level, and no way to hand a pending result to another function. Promises make "a value that isn't here yet" into a **thing** — something you can return, store in an array, pass around, and compose.

The mental model

A restaurant buzzer. You don't get food, you get an object that will eventually mean food — or mean the kitchen ran out. You can put it in your pocket, hand it to a friend, or agree in advance what to do when it goes off.

How it works

Three states, and the transition happens once

A Promise is **pending**, then either **fulfilled** with a value or **rejected** with a reason. It settles exactly once and can never change afterwards. That immutability is what makes promises safe to share: handing the same promise to five functions is fine, because none of them can alter it, and all five get the same answer.

then returns a new promise — that's the whole design

`.then()` doesn't modify the promise, it returns a *new* one. The value your callback returns becomes the new promise's value; if you return a promise, it gets adopted and waited on. This one rule is what makes chaining flat instead of nested, and it's why forgetting to `return` inside a `.then` breaks the chain — the next link receives `undefined` and, worse, doesn't wait.

Errors fall down the chain until something catches them

A rejection skips every `.then` until it finds a `.catch`. One catch at the end handles failures from every step above it — the big improvement over callbacks, where each level checked its own error. The failure mode to know: an unhandled rejection. Forget the catch and the error vanishes silently in older environments, or crashes the Node process in current ones.

Starting and awaiting are separate acts

A promise starts running the moment it's created — not when you await it. This is the difference between sequential and concurrent code. `await a(); await b();` starts `a`, waits, then starts `b`: total time is the sum. `Promise.all([a(), b()])` starts both immediately, then waits: total time is the max. Same operations, and on two 1-second calls that's 2 seconds versus 1. It's the most common easy performance win in async JavaScript.

The mechanism

The four combinators, and when each is right: - **`Promise.all`** — all must succeed. Rejects immediately if any one does. Use when you need every result. - **`Promise.allSettled`** — never rejects. Returns a status for each. Use when partial success is acceptable. - **`Promise.race`** — first to *settle* wins, success or failure. Use for timeouts. - **`Promise.any`** — first to *succeed* wins. Rejects only if all fail. Use for redundant sources. The `all` versus `allSettled` choice is the one that matters most in practice: `all` loses the results that did succeed.

flowchart LR
    A[pending] -->|resolve| B[fulfilled]
    A -->|reject| C[rejected]
    B --> D[then callback queued as microtask]
    C --> E[catch callback queued as microtask]
    D --> F[new promise]
    E --> F
Diagram source for Callbacks & Promises.

What people get wrong

A promise runs when you await it.
It starts as soon as it's created. await only decides when you stop and collect the result. This is exactly why awaiting inside a loop is sequential and Promise.all is concurrent — the difference is when the work started.
A .catch at the end catches everything in the chain.
It catches everything upstream of it — but not errors thrown in a .then registered after it, and not from promises never linked into the chain. Errors flow along the chain. A promise you created but never returned or awaited isn't part of it.
async functions can return a plain value.
An async function always returns a promise. Returning 5 returns a promise that fulfils with 5. The keyword wraps the return value. That's why you can await any async function's result even when the body is fully synchronous.
Promise.all is just a tidier way to await several things.
It also changes the failure behaviour: one rejection discards every other result, even ones that succeeded. It rejects on the first failure. If partial results are useful, allSettled is the correct tool.

When not to use it

The source produces many values over time, not one — a stream of events, an infinite feed.
Async iterators or an event emitter. A promise settles once and only once.
You need to cancel work already in flight.
AbortController passed into the underlying API. Promises have no cancellation of their own.

Terms

Pending
Created, not yet settled.
Settled
Either fulfilled or rejected. Permanent.
Thenable
Any object with a .then method. Promise machinery treats these like promises, which is how interop with older libraries works.
Unhandled rejection
A promise rejected with no catch attached. Crashes the process in current Node.
Microtask
The queue promise callbacks run on — drained before the next timer or I/O callback.
Adoption
Returning a promise from .then makes the outer promise wait for and mirror the inner one.

In an interview

What's the difference between awaiting in a loop and Promise.all?

  • await in a loop is sequential — each iteration waits before starting the next
  • Promise.all starts everything immediately, so total time is the slowest not the sum
  • the promise starts on creation, not on await
  • Promise.all rejects on the first failure; allSettled preserves partial results

How does error handling differ between callbacks and promises?

  • callbacks check an error argument at every level
  • rejections propagate down the chain to the nearest catch
  • a synchronous throw inside .then becomes a rejection automatically
  • unhandled rejections terminate the process in current Node

Can you recall it?

Why is `await a(); await b();` slower than `Promise.all([a(), b()])` when both do the same work?

Sources

Keep track of this

Add The JavaScript Language to your map and Rungsy will schedule reviews so you actually remember it.