Promises
Objectives
By the end of this chapter, you should be able to:
- Explain what a promise represents
- Create a promise with
new Promise(), and resolve or reject it - Handle a promise’s result with
.then()and its failure with.catch() - Chain
.then()calls instead of nesting callbacks - Run several promises at once with
Promise.all()
💡 Why this matters: Promises fix the exact problem from last lesson, deeply nested callbacks, and they’re the foundation
async/await(next lesson) is built on. Understanding promises first makesasync/awaitfeel like a shortcut instead of magic.
What a Promise Is
A promise is an object representing a value that isn’t ready yet, but will be, eventually, either successfully or with an error. Instead of passing a callback into a function, the function returns a promise, and you attach what should happen next onto that promise.
function fetchUserDelayed() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ name: "Sam", id: 1 });
}, 1000);
});
}
fetchUserDelayed().then((user) => {
console.log("Got user:", user.name);
});
// (one second later) Got user: Sam
new Promise(executor) takes a function (the executor) that receives two functions of its own, resolve and reject. Calling resolve(value) marks the promise as successful with that value. .then(callback) runs callback with that value, once the promise resolves.
Handling Failure with .catch()
Calling reject(error) instead of resolve() marks the promise as failed. .catch(callback) handles that case.
function fetchWillFail() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("Something went wrong"));
}, 1000);
});
}
fetchWillFail()
.then((result) => {
console.log("this never runs");
})
.catch((error) => {
console.log("Caught:", error.message);
});
// Caught: Something went wrong
When a promise rejects, its .then() callback is skipped entirely, and the error jumps straight to the nearest .catch(). This is similar to how throw skips the rest of a try block and jumps to catch (Course 1, Error Handling), promises apply that same idea to asynchronous code.
Running Code Either Way: .finally()
.finally(callback) runs callback once the promise settles, whether it resolved or rejected. It’s useful for cleanup that needs to happen regardless of the outcome, like hiding a loading spinner.
fetchUserDelayed()
.then((user) => console.log("Got", user.name))
.catch((err) => console.log("Error:", err.message))
.finally(() => console.log("Done, either way"));
Chaining Instead of Nesting
Each .then() returns a new promise, so you can chain them in sequence, flat, instead of nesting one callback inside another.
function step1() {
return new Promise((resolve) => setTimeout(() => resolve("Step 1 done"), 1000));
}
function step2(prevResult) {
return new Promise((resolve) => setTimeout(() => resolve(prevResult + " -> Step 2 done"), 1000));
}
function step3(prevResult) {
return new Promise((resolve) => setTimeout(() => resolve(prevResult + " -> Step 3 done"), 1000));
}
step1()
.then((result) => step2(result))
.then((result) => step3(result))
.then((result) => console.log("Chain result:", result));
// (three seconds later) Chain result: Step 1 done -> Step 2 done -> Step 3 done
Compare this to the nested version from last lesson. Same three sequential steps, but the shape stays flat no matter how many steps you add, each .then() sits at the same level as the one before it, instead of nesting one level deeper.
Running Promises at the Same Time: Promise.all()
Sometimes steps don’t depend on each other and can run simultaneously instead of one after another. Promise.all(arrayOfPromises) waits for every promise in the array to resolve, then resolves with an array of all their results, in the same order they were passed in.
const p1 = new Promise((resolve) => setTimeout(() => resolve("A"), 3000));
const p2 = new Promise((resolve) => setTimeout(() => resolve("B"), 1000));
const p3 = new Promise((resolve) => setTimeout(() => resolve("C"), 2000));
Promise.all([p1, p2, p3]).then((results) => {
console.log(results);
});
// (three seconds later) ["A", "B", "C"]
Even though p2 finishes first and p3 finishes second, the result array keeps the original order, [p1's result, p2's result, p3's result]. The whole thing takes as long as the slowest promise (three seconds here), not the sum of all three, since they run at the same time instead of one after another.
Try It
Starter code for all four exercises:
function delayedValue(value, ms) {
return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}
- Call
delayedValue("done", 500)and use.then()to log the result. - Write a function
delayedFailure(ms)that returns a promise which always rejects withnew Error("failed")aftermsmilliseconds. Call it and handle the error with.catch(). - Chain two calls to
delayedValue(), using the first result to build a string passed into the second, and log the final combined result with.then(). - Call
delayedValue()three times with different values and delays, collect all three promises in an array, and usePromise.all()to log all three results at once.
Recap
- A promise represents a value that isn’t ready yet.
new Promise((resolve, reject) => ...)creates one,resolve()marks success,reject()marks failure. .then(callback)handles a resolved value..catch(callback)handles a rejected error..finally(callback)runs either way.- Chaining
.then()calls keeps sequential async steps flat, instead of nesting callbacks deeper with each step. Promise.all(arrayOfPromises)runs several promises at once and resolves once all of them finish, with results in the original order.
Next lesson: async/await, syntax that makes promise-based code read almost like ordinary, top-to-bottom code.