Skip to content

Node.js Promises

Promises give asynchronous code a cleaner, chainable structure than nested callbacks. This lesson covers creating, chaining, and combining promises, the foundation for the async/await syntax covered next.

What Is a Promise?

A promise is an object representing a value that isn't available yet but will be at some point, either successfully ("resolved") or unsuccessfully ("rejected"). Instead of passing a callback into a function, that function returns a promise, and you attach .then() for success and .catch() for failure.

Promises can be chained: each .then() returns a new promise, letting you express a sequence of async steps without nesting, flattening what would otherwise be callback hell into a readable, linear chain.

fetch('https://api.example.com/users/1')
  .then((response) => response.json())
  .then((user) => {
    console.log('User:', user.name);
  })
  .catch((error) => {
    console.error('Request failed:', error.message);
  });

Each .then() step receives the resolved value from the previous one. A single .catch() at the end handles a rejection from any step in the chain.

Promise Syntax

new Promise((resolve, reject) => { ... })
promise.then(onFulfilled, onRejected)
promise.catch(onRejected)
promise.finally(onFinally)
Promise.all([p1, p2, p3])
Promise.race([p1, p2])
  • resolve(value) fulfills the promise successfully; reject(error) fails it.
  • .then() handles success (and optionally failure); .catch() specifically handles failure.
  • .finally() runs regardless of outcome, useful for cleanup like hiding a loading state.
  • Promise.all() waits for every promise to resolve, rejecting immediately if any one rejects.

Promises Cheatsheet

The Promise API methods you'll use in almost every async Node.js codebase.

Method Behavior
Promise.resolve(value) Creates an already-resolved promise
Promise.reject(error) Creates an already-rejected promise
Promise.all([...]) Resolves when all resolve, rejects on first rejection
Promise.allSettled([...]) Waits for all, regardless of success or failure
Promise.race([...]) Settles as soon as the first promise settles
Promise.any([...]) Resolves as soon as any promise resolves

Creating a Promise Manually

Most of the time you'll consume promises returned by other functions (fetch, fs.promises.readFile, database drivers). Occasionally you need to wrap a callback-based API yourself using the Promise constructor.

function wait(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

wait(1000).then(() => console.log('1 second passed'));

Running Promises in Parallel

Promise.all() is one of the most useful combinators: it lets you kick off several independent async operations at once and wait for all of them together, instead of awaiting them one after another and wasting time.

const [user, posts, comments] = await Promise.all([
  fetchUser(userId),
  fetchPosts(userId),
  fetchComments(userId),
]);

This runs all three requests concurrently. If they were awaited sequentially instead, the total time would be the sum of all three, not the maximum.

Common Mistakes

  • Forgetting a .catch() (or try/catch with async/await), letting rejections become unhandled promise rejections.
  • Awaiting promises sequentially with separate await statements when they could safely run in parallel with Promise.all().
  • Using Promise.all() when one failure shouldn't cancel the others, Promise.allSettled() is usually the better fit there.
  • Returning a promise from inside .then() without returning it (forgetting the return keyword), breaking the chain.

Key Takeaways

  • A promise represents a value that will be available later, either resolved or rejected.
  • .then()/.catch()/.finally() let you chain async steps without nested callbacks.
  • Promise.all() runs promises in parallel and waits for all to resolve.
  • Promise.allSettled() waits for every promise regardless of individual failures.

Pro Tip

Reach for Promise.all() any time you're about to await multiple independent operations one after another. If they don't depend on each other's results, running them concurrently is almost always faster with no downside.