Node.js · Async Patterns

Promises

A promise is an object for a value that will be ready later — or an error. Promises fix callback hell, compose well, and are what async/await is built on.

Intermediate 5 min Lesson 16 of 37Examples target Node.js 22 LTS.
Before you start:Callbacks
0/37 in Node.js
On this page · 5 min · Intermediate

Learning objectives#

  • Create promises and consume them with then/catch/finally
  • Chain dependent steps
  • Use promise-based core modules
  • Handle unhandled rejections

Explanation#

A promise is pending, then either fulfilled (with a value) or rejected (with an error) — once.

Create one with new Promise((resolve, reject) => ...). Usually you get promises from APIs instead: fs/promises, fetch, database drivers.

then(fn) receives the value and returns a new promise, so you can chain steps; catch(fn) handles errors from any earlier step; finally(fn) runs either way (close connections, hide spinners).

Combinators: Promise.all (all succeed), allSettled (all finish), race (first to settle), any (first to succeed).

An unhandled rejection crashes Node by default. Always end chains with catch (or use try/catch with await), and log process "unhandledRejection" events as a safety net.

Syntax#

JavaScript Illustrative
getUser(7)
  .then(user => getOrders(user.id))
  .then(orders => console.log(orders.length))
  .catch(err => console.error(err.message))
  .finally(() => db.close());

Code example: Creating, chaining and combining promises#

JavaScriptpromises.js Illustrative
import { setTimeout as delay } from 'node:timers/promises';


function checkStock(product, qty) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (qty <= product.stock) resolve({ ...product, reserved: qty });
      else reject(new Error(`Only ${product.stock} ${product.name} left`));
    }, 30);
  });
}


const pen = { name: 'Pen', price: 20, stock: 10 };


// Chain: each then receives the previous result
checkStock(pen, 3)
  .then(item => item.price * item.reserved)
  .then(total => console.log('Total:', total))
  .catch(err => console.log('Failed:', err.message))
  .finally(() => console.log('Stock check 1 finished'));


await delay(50);


checkStock(pen, 30)
  .then(item => console.log('Reserved', item.reserved))
  .catch(err => console.log('Failed:', err.message))
  .finally(() => console.log('Stock check 2 finished'));


await delay(50);


// Combinators
const fast = delay(10, 'fast mirror');
const slow = delay(100, 'slow mirror');
console.log('race →', await Promise.race([fast, slow]));
console.log('any  →', await Promise.any([Promise.reject(new Error('down')), delay(20, 'backup')]));
Expected output
Total: 60
Stock check 1 finished
Failed: Only 10 Pen left
Stock check 2 finished
race → fast mirror
any  → backup

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. checkStock wraps a timer in new Promise, resolving or rejecting once.
  2. The first chain computes the total in a then and prints it; finally runs at the end.
  3. The second check rejects, so the then is skipped and catch handles the error.
  4. timers/promises provides a promise-based setTimeout (delay).
  5. race returns the first to settle; any returns the first to succeed, ignoring rejections.

Real-world use case#

A checkout reserves stock, charges the card and creates the order as a promise chain; finally always releases the database connection, whether the checkout succeeded or failed.

Common mistakes#

Forgetting to return in then

then(user => { getOrders(user.id); }) passes undefined to the next step. Return the promise.

Missing catch

An unhandled rejection crashes the process in modern Node. Every chain needs error handling.

Wrapping promises in new Promise

new Promise(resolve => fetch(url).then(resolve)) is the “explicit construction” anti-pattern and loses errors. Return the existing promise.

Best practices#

  • Return promises from then callbacks
  • End every chain with catch
  • Use finally for cleanup
  • Prefer promise-based core modules

Remember this#

pending → fulfilled or rejected (once).

then chains, catch handles, finally cleans up.

Unhandled rejection = crash.

Practice exercise#

Your task: Write a sleep(ms) function that returns a promise, then log "A", wait 100 ms, log "B".

Need a hint?
return new Promise(resolve => setTimeout(resolve, ms)).
Show solution
JavaScript Illustrative
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));


console.log('A');
await sleep(100);
console.log('B');

Mini quiz#

Promises quizMultiple choice Question 1 of 3

Which method runs whether the promise was fulfilled or rejected?

Score so far: 0/0

Want more? Practise all Node.js MCQs · Node.js interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.