Skip to content
Lesson 4 of 15

Step 1 of 7 · Reading · ~4 min

Learn

Async JavaScript

Promises

A promise is an object that stands for a value that is not ready yet. By the end of this lesson you can create one, chain steps on it, send errors down the chain and recover from them, and predict the order in which promise code runs.

Why they exist

Timers, files and network calls finish later. Old code passed a callback to each and nested the next step inside it, which grew into a pyramid where every level repeated its own error check. A promise is a value you can hold, pass around and attach steps to, so the steps read top to bottom.

Creating one

javascript

Two pieces here come from outside this lesson: setTimeout(fn, ms) calls fn once, ms milliseconds later, and new Error("text") makes an error object whose .message is the text. A throw stops the current function and raises such an error; the try/catch/throw lesson covers it fully.

The function you hand to new Promise is the executor. It runs immediately and synchronously, and receives two functions. Call resolve(value) to finish successfully or reject(error) to fail. A promise is in one of three states: pending (not finished), fulfilled (finished with a value) or rejected (failed with a reason). It leaves pending once, and after that its state never changes, so a second resolve call is silently ignored. An executor that never calls either leaves the promise pending forever.

then returns a new promise

.then(onFulfilled) registers a step. The key fact: it returns a new promise, and whatever your step returns decides how that one settles.

  • return a plain value: the new promise fulfils with it
  • return another promise: the new promise adopts that promise's outcome (it waits for it)
  • throw: the new promise is rejected with what you threw
javascript

Errors skip forward until a catch

A rejection travels down the chain, skipping every .then step, until it meets a handler for errors. .catch(fn) is shorthand for .then(undefined, fn). The important detail is what happens afterwards: if the .catch handler returns normally, the chain is fulfilled again with that return value. A handler that wants the failure to continue must throw again or return a rejected promise. .finally(fn) runs either way and does not change the outcome.

Order: promise steps run later, never now

Even for an already fulfilled promise, .then steps are queued as microtasks. They run after the current synchronous code finishes and before timers.

javascript

Combining several promises

  • Promise.all([a, b]) fulfils with an array of results in input order, or rejects as soon as any one rejects.
  • Promise.allSettled([a, b]) waits for all and gives { status, value } or { status, reason } for each. It never rejects.
  • Promise.race([a, b]) settles like the first one to settle.

Common mistakes

  • Not returning inside .then. The next step then receives undefined, and the chain no longer waits for your inner promise.
  • Believing a .catch stops the chain. It recovers it: whatever it returns reaches the next .then.
  • Calling resolve and then expecting later code in the executor not to run. It does run.
  • Leaving a rejection with no handler. Node prints a warning, and newer versions crash.
  • Expecting .then code to run before the line after it. It never does.

Your exercise

Chain promises so that a failure triggers another attempt. The starter supplies a task that fails a set number of times and prints the outcome; you write the function in between. The usual slip is forgetting to return the inner promise.

Up nextasync / awaitAsync JavaScript

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…