Promises
Objectives
By the end of this lesson, you should be able to:
- Explain what a promise represents and its three possible states
- Chain
.then(),.catch(), and.finally()correctly - Run several promises at once with
Promise.all()
💡 Why this matters: Almost everything a backend does, querying a database, reading a file, calling another API, takes time and doesn’t have an answer immediately. Promises are JavaScript’s way of representing “this value isn’t ready yet, but here’s what to do once it is.”
⚠️ A note on verification: every snippet and output in this lesson was actually run with Node.js.
What a Promise Represents
A promise is an object representing a value that will exist eventually, either successfully (resolved) or unsuccessfully (rejected). It starts pending, and settles into exactly one of those two outcomes, never both, and never more than once.
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id <= 0) {
reject(new Error("invalid id"));
} else {
resolve({ id, name: "Erin Castillo" });
}
}, 10);
});
}
fetchUser(1).then((user) => {
console.log("resolved:", user);
});
resolved: { id: 1, name: 'Erin Castillo' }
new Promise((resolve, reject) => {...}) wraps asynchronous work (here, setTimeout standing in for something slow, like a real database query). Calling resolve(value) settles the promise successfully, reject(error) settles it as a failure. .then(callback) runs once the promise resolves, receiving the resolved value.
Chaining
fetchUser(1)
.then((user) => {
console.log("first:", user.name);
return fetchUser(2);
})
.then((user) => {
console.log("second:", user.name);
})
.catch((err) => {
console.log("error:", err.message);
})
.finally(() => {
console.log("chain finished");
});
first: Erin Castillo
second: Erin Castillo
chain finished
Returning a new promise from inside .then() chains it, the next .then() waits for that returned promise too, rather than running immediately. .catch() handles a rejection anywhere earlier in the chain, .finally() runs regardless of whether the chain succeeded or failed, the same three-way pattern as try/catch/finally from the previous lesson.
Handling Rejection
fetchUser(-1)
.then((user) => console.log("resolved:", user))
.catch((err) => console.log("caught:", err.message));
caught: invalid id
fetchUser(-1) rejects (id is invalid), so the .then() callback never runs at all, execution jumps straight to .catch().
Running Several Promises at Once
function fetchUser(id) {
return new Promise((resolve) => {
setTimeout(() => resolve({ id, name: `User ${id}` }), 10);
});
}
Promise.all([fetchUser(1), fetchUser(2), fetchUser(3)]).then((users) => {
console.log(users.map((u) => u.name));
});
[ 'User 1', 'User 2', 'User 3' ]
Promise.all([...]) waits for every promise in the array to resolve, then resolves itself with an array of all the results, in the same order they were passed in, not necessarily the order they finished. This is far faster than awaiting each one sequentially when they don’t depend on each other, all three fetchUser calls here run concurrently, not one after another.
Try It
- Write a function
checkStock(productId)returning a promise that resolves with{ inStock: true }after a shortsetTimeout, and call it with.then(). - Chain a second
.then()onto question 1 that logs a message based on the resolved value. - Write a version of
checkStockthat rejects ifproductIdis negative, and handle that rejection with.catch(). - Use
Promise.all()to run three of yourcheckStockcalls at once, and log all three results together.
Recap
- A promise represents a value that isn’t ready yet, starting pending, settling into either resolved or rejected.
.then()handles success,.catch()handles failure,.finally()runs either way, and returning a promise from.then()chains it.Promise.all([...])runs multiple promises concurrently, resolving once all of them have, in their original order.
Next lesson: async/await, writing asynchronous code that reads like synchronous code.