NodeJSPromises in Node.js

Promises in Node.js

A Promise is an object representing a value that isn't ready yet — a placeholder for the eventual result (or failure) of an async operation. Promises replaced the callback convention as Node's primary async abstraction because they chain cleanly, unify error handling, and compose — letting you combine many async operations with one expression.

The three states

State

Meaning

Settled?

pending

The operation is still in progress

No

fulfilled

Completed successfully, has a value

Yes

rejected

Failed, has a reason (an error)

Yes

A promise settles once — and only once
A promise transitions from *pending* to either *fulfilled* or *rejected* exactly one time, then is frozen forever. Resolving an already-settled promise does nothing. This immutability is what makes promises safe to pass around and `await` repeatedly — the result can't change underneath you.
Consuming a promise

JS
fetchUser(1)
  .then((user) => console.log('got', user.name))   // fulfilled path
  .catch((err) => console.error('failed', err))     // rejected path
  .finally(() => console.log('done either way'))     // always runs

Method

Runs when

Returns

.then(onOk)

Promise fulfills

A new promise (chainable)

.then(onOk, onErr)

Fulfills or rejects

A new promise

.catch(onErr)

Promise rejects

A new promise

.finally(fn)

Settles either way

A pass-through promise

Chaining: each then returns a new promise

The power of promises is that .then returns another promise, so steps line up flat. Return a value to pass it on; return a promise to wait for it first:

JS
fetchUser(1)
  .then((user) => user.id)                 // return a value → next .then gets it
  .then((id) => fetchOrders(id))           // return a promise → chain waits for it
  .then((orders) => console.log(orders.length))
  .catch((err) => console.error(err))      // one catch for the whole chain
Always return inside `.then` — or the chain breaks
Forgetting to `return` a promise inside `.then` is the #1 promise bug: the next `.then` runs immediately with `undefined` instead of waiting, and errors from the un-returned promise escape the chain's `.catch` (becoming an unhandled rejection). Rule: if a `.then` callback starts an async operation, **return** it.
Creating a promise

Most of the time you consume promises from APIs. When you must wrap a non-promise API (like a callback or an event), use the Promise constructor — calling resolve on success, reject on failure:

JS
function delay(ms, value) {
  return new Promise((resolve, reject) => {
    if (ms < 0) return reject(new Error('negative delay'))
    setTimeout(() => resolve(value), ms)
  })
}

delay(100, 'hi').then(console.log)   // 'hi' after 100ms
Prefer wrapping at the edges only
Use `new Promise(...)` only to *adapt* a non-promise API (events, callbacks). Inside already-promise-based code, never wrap — it's the "explicit promise construction antipattern". And for callbacks specifically, `util.promisify` is cleaner than a hand-rolled constructor. See [util.promisify](/nodejs/promisify).
Combining promises

The static combinators are where promises outshine callbacks — running many operations together with precise semantics:

Combinator

Resolves when

Rejects when

Promise.all([...])

ALL fulfill → array of values

ANY rejects (fails fast)

Promise.allSettled([...])

ALL settle → array of {status,…}

Never — reports each outcome

Promise.race([...])

The FIRST settles (ok or fail)

If the first to settle rejects

Promise.any([...])

The FIRST to fulfill

If ALL reject (AggregateError)

JS
// Run three in parallel, fail if any fails:
const [a, b, c] = await Promise.all([getA(), getB(), getC()])

// Run three, get every outcome regardless of failures:
const results = await Promise.allSettled([getA(), getB(), getC()])
results.forEach((r) => {
  if (r.status === 'fulfilled') console.log(r.value)
  else console.error(r.reason)
})
`Promise.all` rejects on the FIRST failure
If any input promise rejects, `Promise.all` rejects immediately with that error — and the other operations keep running but their results are discarded (and their rejections may go unhandled). When you need *all* outcomes regardless of individual failures, use `Promise.allSettled`.
Unhandled rejections crash modern Node
Every promise needs a rejection handler
A rejected promise with no `.catch` (or no surrounding `try/catch` when awaited) triggers an `unhandledRejection` — and in modern Node this **terminates the process** by default. Always attach a `.catch`, await inside `try/catch`, or handle it with combinators. The `process.on('unhandledRejection', …)` net is for logging before exit, not for ignoring (see [Handling error Events](/nodejs/error-events)).
Next
The syntax that makes promises read like synchronous code: [async / await in Node.js](/nodejs/async-await-node).