Promises — States, Chaining and the Combinators
IntermediateA promise is a value that will exist later. It has three states, settles exactly once, and chains through .then — and the four combinators cover almost every multi-request situation.
Overview
Promises replaced callback nesting with a value you can pass around, return from functions, and combine. A promise is pending, then either fulfilled or rejected, and once settled it never changes. The chaining rule that matters: whatever you return from a .then becomes the next promise's value, and if you return a promise it is waited for. Most people write async/await day to day, but promises are what await operates on, and the combinators — all, allSettled, race, any — have no await equivalent.
States and Chaining
Each .then returns a new promise, which is what makes chains work and why forgetting to return inside a .then breaks them.
const p = fetch('/api/user') // pending
.then(res => res.json()) // returns a promise -> awaited
.then(user => user.name) // returns a value -> wrapped
.catch(err => 'anonymous') // handles anything above
.finally(() => setLoading(false)) // always runs, passes value through
// The classic bug — no return, so the chain gets undefined:
fetchUser()
.then(user => {
fetchOrders(user.id) // MISSING return
})
.then(orders => orders.length) // orders is undefined
// A promise settles once. Later calls are ignored:
new Promise((resolve, reject) => {
resolve('first')
resolve('second') // ignored
reject(new Error()) // ignored
})The Four Combinators
Choosing the right one is usually the whole design decision for a screen that loads several things.
// all — every one must succeed; rejects on the first failure
const [user, orders] = await Promise.all([
fetchUser(id), fetchOrders(id),
])
// allSettled — never rejects; you inspect each result
const results = await Promise.allSettled(urls.map(fetchOne))
const ok = results.filter(r => r.status === 'fulfilled').map(r => r.value)
const failed = results.filter(r => r.status === 'rejected')
// race — first to SETTLE wins, success or failure. Used for timeouts.
await Promise.race([
fetch(url),
new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), 5000)),
])
// any — first to SUCCEED; rejects only if all fail
await Promise.any([fetchPrimary(), fetchMirror()])Sequential vs Parallel
The most common performance mistake in async code is awaiting independent requests one after another.
// Sequential — 900ms if each takes 300ms
const user = await fetchUser()
const orders = await fetchOrders()
const cart = await fetchCart()
// Parallel — 300ms; they do not depend on each other
const [user, orders, cart] = await Promise.all([
fetchUser(), fetchOrders(), fetchCart(),
])
// Start them, then await — same effect, sometimes clearer
const userP = fetchUser()
const ordersP = fetchOrders()
const user = await userP
const orders = await ordersP
// Sequential is correct when there IS a dependency:
const user = await fetchUser()
const orders = await fetchOrders(user.id) // needs userKey Points to Remember
- 1A promise settles exactly once — later resolve or reject calls are ignored
- 2Every .then returns a new promise; forgetting to return inside one breaks the chain with undefined
- 3Promise.all rejects on the first failure; allSettled never rejects and reports each outcome
- 4race settles on the first result either way — which is what makes it a timeout; any waits for the first success
- 5Awaiting independent requests in sequence is the most common async performance bug — use Promise.all
Interview Questions
Sign in to ask AriaWhat is the difference between Promise.all and Promise.allSettled?
How would you add a 5-second timeout to a fetch call?
Why is awaiting three independent API calls on separate lines slower than Promise.all?
Ask Aria about Promises — States, Chaining and the Combinators
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.