Promises
Settlement, chaining, errors, Promise API helpers, and how promises plug into the microtask queue — without cargo-cult async.
- promises
- async
- javascript
A Promise is an object for a value that may arrive later. It starts pending, then fulfills or rejects once. Handlers run as microtasks — that’s why they interleave with timers the way they do (see Event Loop).
Callbacks still work. Promises make composition and error paths less of a pyramid of doom.
The contract
const p = new Promise((resolve, reject) => {
// executor runs sync
setTimeout(() => resolve('done'), 100);
});
p.then((value) => console.log(value));
resolve(x)→ fulfilled withx(unlessxis a thenable — then it adopts that).reject(err)→ rejected.- Calling resolve/reject again is a no-op.
.then, .catch, .finally always return a new promise. That’s chaining.
Chaining (the useful part)
fetch('/api/user')
.then((res) => {
if (!res.ok) throw new Error(String(res.status));
return res.json(); // return a promise → next then waits
})
.then((user) => user.name)
.catch((err) => {
console.error(err);
return 'anonymous';
})
.finally(() => {
// cleanup; does not change fulfillment value unless it throws
});
Return a value → next then gets it.
Return a promise → next then waits.
Throw or return a rejected promise → control jumps to the next catch.
Errors fall down the chain
One catch at the end is often enough for a linear pipeline. If you recover inside catch by returning a normal value, the chain continues fulfilled. If you rethrow, rejection continues.
Promise.resolve()
.then(() => {
throw new Error('boom');
})
.then(() => console.log('skipped'))
.catch((e) => console.log('handled', e.message));
Promise API (know these cold)
| Helper | Meaning |
|---|---|
Promise.all |
Fail fast on first rejection; all must fulfill |
Promise.allSettled |
Wait for all; get {status, value|reason}[] |
Promise.race |
First settle wins (fulfill or reject) |
Promise.any |
First fulfillment wins; rejects only if all reject |
Promise.resolve / reject |
Wrap a value / reason |
// Parallel work when order of completion doesn’t matter for *starting*
const [a, b] = await Promise.all([fetchA(), fetchB()]);
// Don’t use all if one failure should not cancel the others
const results = await Promise.allSettled([fetchA(), fetchB()]);
Interview trap: Promise.all rejects on the first failure and does not cancel the other fetches — they still run unless you abort them yourself (AbortController).
async/await
async function load() {
try {
const res = await fetch('/api');
return await res.json();
} catch (e) {
// rejects from await land here
throw e;
}
}
// load() always returns a Promise
Sequential await is intentional when B needs A. For independent work, start both first:
const pa = fetchA();
const pb = fetchB();
const [a, b] = await Promise.all([pa, pb]);
Cancellation (reality check)
Promises don’t cancel. Fetch does, with signals:
const ac = new AbortController();
const p = fetch('/slow', { signal: ac.signal });
ac.abort(); // rejects the fetch with AbortError
Mental model for production
- Prefer
async/awaitfor linear control flow; keep.thenwhen mapping streams of promises. - Always decide error policy: fail fast (
all) vs best-effort (allSettled). - Don’t forget the event loop: heavy work after
awaitstill blocks the main thread.
Further reading
Related
Related guides
- client offset scroll Dimensionsclient offset scroll Dimensions explained for frontend engineers — mental model, examples, common mistakes, and interview tips.
- The Event LoopHow JS runtimes schedule work — call stack, macrotasks, microtasks, rendering, and how to talk about it in interviews.
- Pub Sub vs ObserverPub Sub vs Observer explained for frontend engineers — mental model, examples, common mistakes, and interview tips.
- getBoundingClientRectgetBoundingClientRect explained for frontend engineers — mental model, examples, common mistakes, and interview tips.
- Hoisting in JavaScriptHoisting in JavaScript explained for frontend engineers — mental model, examples, common mistakes, and interview tips.