Promises: An IOU for a Future Value

Freshman Year, page 4 of 10. A three-minute read.

Ever been to a restaurant where they give you a little buzzer when you order? The buzzer isn't your food. But it's a promise that you'll get your food. You can go and sit down, chat with friends, and when the buzzer goes off, you collect your meal. Or, sometimes, the buzzer goes off and the staff say: "Sorry, we've run out of chicken."

A JavaScript Promise is exactly that buzzer. It's an object that represents a value that isn't ready yet, but will be at some point in the future (or that will fail).

A promise starts out pending, then either fulfills with a value or rejects with an error

Every promise is in one of three states:

StateMeaningBuzzer equivalent
pendingStill waiting for the resultBuzzer is quiet. Food's cooking.
fulfilledSuccess! The value is readyBuzzer goes off. Food is ready! 🍕
rejectedSomething went wrong"Sorry, we're out of cheese." 😢

Once a promise is fulfilled or rejected, it's settled, and it never changes again.

Using a promise: then, catch and finally

You don't usually create promises yourself (we'll see how in a minute). Mostly, you'll use promises that other functions give you. To say what should happen when a promise succeeds, use .then(). For failures, use .catch(). And for "no matter what," use .finally():

JavaScript
function orderPizza(flavor) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (flavor === "pineapple") {
        reject(new Error("We don't do that here. 😤"));
      } else {
        resolve(`🍕 One ${flavor} pizza!`);
      }
    }, 100);
  });
}

orderPizza("pepperoni")
  .then((pizza) => console.log(`Got it: ${pizza}`))
  .catch((error) => console.log(`Problem: ${error.message}`))
  .finally(() => console.log("Order 1 complete."));

orderPizza("pineapple")
  .then((pizza) => console.log(`Got it: ${pizza}`))
  .catch((error) => console.log(`Problem: ${error.message}`))
  .finally(() => console.log("Order 2 complete."));

It prints

Got it: 🍕 One pepperoni pizza!
Order 1 complete.
Problem: We don't do that here. 😤
Order 2 complete.

(No offense to pineapple pizza fans. Buggsy wrote this example.)

Creating a promise

Let's look at how orderPizza creates its promise:

JavaScript
new Promise((resolve, reject) => {
  // do the slow work here...
  // call resolve(value) if it worked
  // call reject(error) if it failed
});

You give new Promise a function, and JavaScript hands that function two special functions as arguments: resolve and reject. Call resolve(value) to fulfill the promise with a value, or reject(error) to reject it with an error.

A super handy example is a wait function that creates a promise which fulfills after a delay:

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

wait(500).then(() => console.log("Half a second later..."));

It prints

Half a second later...

You'll use this little helper all through this year.

Chaining: escaping the pyramid

Here's the magic of promises. Every .then() returns a new promise, so you can chain them. If your .then() callback returns a value (or another promise), the next .then() in the chain receives it. That lets you do several asynchronous steps in a row, without nesting:

JavaScript
function getUser(id) {
  return new Promise((resolve) => setTimeout(() => resolve({ id, name: "Olivia" }), 50));
}
function getOrders(user) {
  return new Promise((resolve) => setTimeout(() => resolve(["order-17", "order-42"]), 50));
}
function getItems(orderId) {
  return new Promise((resolve) => setTimeout(() => resolve(["waffle", "salsa"]), 50));
}

getUser(1)
  .then((user) => {
    console.log(`1. Found user ${user.name}`);
    return getOrders(user);
  })
  .then((orders) => {
    console.log(`2. Found ${orders.length} orders`);
    return getItems(orders[0]);
  })
  .then((items) => {
    console.log(`3. First order contains: ${items.join(", ")}`);
  })
  .catch((error) => {
    console.log("Something went wrong:", error.message);
  });

It prints

1. Found user Olivia
2. Found 2 orders
3. First order contains: waffle, salsa

The pyramid is gone! The steps line up neatly, one after another, top to bottom. And a single .catch() at the end handles an error from any step. Much better.

Reading is step one

CurlyScript turns these notes into a game: short questions, an explanation after every answer, and a test at the end of each grade. Free, with nothing to sign up for.

Start learning