JavaScript: Promises

Learn how Promises represent future values, how to chain them cleanly, and how to coordinate multiple async operations.

What are Promises?

A Promise is an object that represents the eventual result of an asynchronous operation. Instead of passing a callback into a function, you get a Promise back and attach your handlers to it, making async code far easier to read and chain.

Promise

A Promise is a placeholder for a value that is not available yet but will be at some point in the future.

  • Solves callback hell by returning an object you can chain instead of nesting callbacks
  • A Promise is always in one of three states: pending, fulfilled, or rejected
  • Once settled (fulfilled or rejected) a Promise never changes state again
  • Introduced in ES6 as the foundation for async/await

Promise vs Callback

The Promise version returns an object you chain; the callback version nests.

Promise States

Every Promise moves through a strict lifecycle. Understanding the states helps you predict exactly when your then andcatch handlers will run.

Three States

A Promise starts pending and transitions to either fulfilled or rejected exactly once.

  • pending: initial state, operation is in progress
  • fulfilled: operation succeeded, resolve(value) was called
  • rejected: operation failed, reject(reason) was called
  • Fulfilled and rejected are collectively called "settled", a settled Promise is immutable

Promise States

Inspect each state: fulfilled resolves through then, rejected through catch.

Creating Promises

Pass an executor function to new Promise(). The executor receives two callbacks: resolve to fulfill and reject to fail the Promise.

new Promise(executor)

The executor runs synchronously; resolve and reject are called asynchronously when the result is known.

  • The executor function runs immediately and synchronously
  • Call resolve(value) when the operation succeeds
  • Call reject(new Error(...)) when it fails, always pass an Error object for a stack trace
  • Calling both or calling one twice has no effect, first call wins

Creating a Promise

Validate input synchronously, then resolve or reject based on the async result.

then(), catch(), finally()

These three methods attach handlers to a Promise. Each returns a new Promise, enabling chaining.

Promise Handlers

then handles success, catch handles failure, finally runs regardless of the outcome.

  • then(onFulfilled): runs when the Promise resolves, receives the fulfilled value
  • catch(onRejected): runs when the Promise rejects, receives the error
  • finally(fn): runs always, ideal for cleanup like hiding a loading spinner
  • All three return a new Promise, so they can be chained further

then / catch / finally

Chain handlers to process the result, catch any error, and always clean up.

Promise Chaining

Because then() always returns a new Promise, you can chain multiple async steps in a flat sequence instead of nesting them.

Promise Chaining

Returning a value from then() passes it as the input to the next then() in the chain.

  • Return a plain value from then(): it becomes the resolved value of the next step
  • Return a Promise from then(): the chain waits for it to settle before continuing
  • Throw inside then(): skips all remaining then()s and jumps to catch()
  • One catch() at the end handles errors from any step in the chain

Promise Chain

Three dependent async steps written as a flat chain instead of nested callbacks.

Error Handling in Promises

A rejection or a thrown error anywhere in a chain propagates down, skipping allthen() handlers, until it reaches acatch().

Promise Error Propagation

Errors travel down the chain automatically, you do not need a check in every then().

  • A rejection jumps over all then()s and lands in the nearest catch()
  • After catch() handles an error, the chain continues normally in the next then()
  • Re-throw inside catch() if you cannot handle the error there
  • An unhandled Promise rejection triggers a warning (Node) or a console error (browser)

Error Propagation Through a Chain

The error jumps from step 2 directly to catch, skipping step 3.

Promise.all()

Promise.all() takes an array of Promises and resolves with an array of all their results, but rejects immediately if any one of them rejects.

Promise.all()

Use Promise.all() to run independent async operations in parallel and wait for all of them.

  • Resolves when ALL Promises fulfill, result is an array in the same order as the input
  • Rejects as soon as ANY Promise rejects, other pending Promises are ignored
  • Best for: fetching multiple resources at once when you need all of them to succeed
  • If order matters, the result array preserves input order regardless of which resolved first

Promise.all()

Fetch three resources in parallel; one rejection fails the whole batch.

Promise.race()

Promise.race() resolves or rejects as soon as the first Promise in the array settles, the rest are ignored.

Promise.race()

Use Promise.race() to implement timeouts or take the fastest response from multiple sources.

  • Settles with the first Promise to settle, whether fulfilled or rejected
  • Classic use: pair a real fetch with a timeout Promise to enforce a deadline
  • Does not cancel the slower Promises, they keep running in the background

Promise.race() Timeout Pattern

Race the real request against a timeout, whichever settles first wins.

Promise.allSettled()

Promise.allSettled() waits for every Promise to finish and returns a result object for each, whether it fulfilled or rejected. It never rejects itself.

Promise.allSettled()

Use allSettled() when you need the outcome of every operation, even if some fail.

  • Each result has a status field: "fulfilled" or "rejected"
  • Fulfilled results also have a value field
  • Rejected results also have a reason field
  • Never rejects, safe to use without a catch(), though one is still good practice

Promise.allSettled()

Inspect every outcome, partial failures do not hide successful results.

Promise.any()

Promise.any() resolves as soon as the first Promise fulfills, ignoring rejections. It only rejects if every single Promise rejects.

Promise.any()

Use Promise.any() when you have multiple sources and only need one to succeed.

  • Resolves with the first fulfilled value, rejections are ignored until all fail
  • If all reject, it rejects with an AggregateError containing every rejection reason
  • Opposite of Promise.all(): any wins vs all must win
  • Good for: trying multiple CDNs or API mirrors and using whichever responds first

Promise.any()

First fulfillment wins, mirror 1 failing does not matter because mirror 2 succeeds.

Promise.resolve() and Promise.reject()

These shortcuts create an already-settled Promise without writing a fullnew Promise() constructor.

Static Shortcuts

Use Promise.resolve() and Promise.reject() to wrap known values into a Promise quickly.

  • Promise.resolve(value): returns an already-fulfilled Promise
  • Promise.reject(reason): returns an already-rejected Promise
  • Useful in tests, stubs, and functions that sometimes return synchronously but must always return a Promise
  • If you pass an existing Promise to Promise.resolve(), it is returned as-is

Promise.resolve() and Promise.reject()

Return a resolved Promise for cached data so callers always get a Promise.

Converting Callbacks to Promises

Older APIs use callbacks. You can wrap them in a Promise to use them in modern async chains or withasync/await.

Promisification

Wrapping a callback-based function in new Promise() is called promisification.

  • Create a new Promise whose executor calls the original callback-based function
  • In the callback: reject on error, resolve on success
  • Node.js provides util.promisify(fn) to do this automatically for error-first callbacks
  • Once promisified, the function works with .then() and async/await

Promisifying a Callback

Wrap the callback pattern in new Promise so it can be chained or awaited.

MethodInputResolves whenRejects when
Promise.all()Array of PromisesALL fulfillANY rejects
Promise.race()Array of PromisesFirst to settle (fulfills)First to settle (rejects)
Promise.allSettled()Array of PromisesALL settle (never rejects)Never
Promise.any()Array of PromisesFirst to fulfillALL reject (AggregateError)

Knowledge Check

1. Which Promise state means the operation is still in progress?

2. What does the then() method receive as its argument?

3. Where should you place a catch() in a Promise chain to handle errors from any step?

4. What does Promise.all() do when one of the input Promises rejects?

5. Which Promise method returns results for ALL Promises, whether they fulfilled or rejected?

6. What does Promise.race() resolve with?

7. What does Promise.any() do if ALL input Promises reject?