Home/Learn/JavaScript & TypeScript/Promises — States, Chaining and the Combinators

Promises — States, Chaining and the Combinators

Intermediate
Async

A 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.

Chaining, and the missing return
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, allSettled, race, any
// 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.

900ms or 300ms, same code shape
// 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 user

Key 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 Aria
1

What is the difference between Promise.all and Promise.allSettled?

Medium
2

How would you add a 5-second timeout to a fetch call?

Medium
3

Why is awaiting three independent API calls on separate lines slower than Promise.all?

Easy

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.

Loading discussion…