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).
Every promise is in one of three states:
| State | Meaning | Buzzer equivalent |
|---|---|---|
| pending | Still waiting for the result | Buzzer is quiet. Food's cooking. |
| fulfilled | Success! The value is ready | Buzzer goes off. Food is ready! 🍕 |
| rejected | Something 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():
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:
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:
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:
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.