15. Promise
A Promise is "a box that will eventually hold the result of an async operation". It has three states.
What you'll learn
- 1Know the three Promise states (pending, fulfilled, rejected).
- 2Create your own Promise with `new Promise`.
- 3Handle results with `then`, `catch`, `finally`.
- 4Flatten callback hell with chaining.
- 5Tell `Promise.all` and `Promise.race` apart.
Overview
A Promise is "a box that will eventually hold the result of an async operation". It has three states.
Core Concepts
1. What is a Promise?
A Promise is "a box that will eventually hold the result of an async operation". It has three states.
- pending: not finished yet
- fulfilled: success β has a value
- rejected: failure β has a reason (error)
const p = new Promise((resolve, reject) => {
setTimeout(() => resolve(42), 100);
});Calling `resolve` settles it as fulfilled; `reject` settles it as rejected. Once settled, the state never changes again.
2. then / catch / finally
p.then((value) => console.log(value))
.catch((err) => console.error(err))
.finally(() => console.log("done"));Whatever a `then` callback returns is passed to the next `then`. Errors flow into the nearest `catch`.
3. Chaining
The callback-hell example rewritten with Promises becomes flat.
step("1").then(() => step("2")).then(() => step("3"));If a `then` returns a Promise, the next `then` waits for it to settle.
4. Promise.all / Promise.race
- `Promise.all([p1, p2, ...])`: succeeds only if all succeed; rejects on the first failure. Result is an array.
- `Promise.race([p1, p2, ...])`: settles with the first one to settle (either way).
- `Promise.allSettled`: waits for all to settle and returns an array of state objects.
Examples
| File | What it covers |
|---|---|
| 01_new_promise.js | Building a Promise directly |
| 02_then_catch.js | Using then/catch/finally |
| 03_chaining.js | Sequential execution by chaining |
| 04_all_race.js | Promise.all / Promise.race |
src/01_new_promise.js
/**
* Represent an async operation with new Promise.
*/
const promise = new Promise((resolve, reject) => {
const ok = Math.random() > 0.3;
setTimeout(() => {
if (ok) resolve("success!");
else reject(new Error("failure..."));
}, 200);
});
promise
.then((value) => console.log("result:", value))
.catch((err) => console.error("error:", err.message));
src/02_then_catch.js
/**
* Show the flow of then / catch / finally.
*/
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id <= 0) reject(new Error("invalid ID"));
else resolve({ id, name: "Jimin" });
}, 100);
});
}
fetchUser(1)
.then((user) => console.log("user:", user))
.catch((err) => console.error(err.message))
.finally(() => console.log("request finished"));
fetchUser(-1)
.then((user) => console.log(user))
.catch((err) => console.error("error:", err.message))
.finally(() => console.log("second request finished"));
src/03_chaining.js
/**
* Run async steps sequentially via chaining.
*/
function step(name, ms) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`${name} done`);
resolve(name);
}, ms);
});
}
step("A", 100)
.then((prev) => step(`B(after ${prev})`, 100))
.then((prev) => step(`C(after ${prev})`, 100))
.then((last) => console.log("final:", last));
src/04_all_race.js
/**
* Compare Promise.all and Promise.race.
*/
function delay(ms, value) {
return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}
Promise.all([delay(100, "A"), delay(200, "B"), delay(150, "C")]).then((values) => {
console.log("all:", values);
});
Promise.race([delay(100, "X"), delay(50, "Y"), delay(80, "Z")]).then((value) => {
console.log("race winner:", value);
});
Common Mistakes
- Not returning a value in `then` and getting undefined in the next `then`.
- Skipping `catch` and triggering an unhandled rejection.
- Forgetting that `Promise.all` fails as soon as one input fails.
- Not realizing that throwing inside a `new Promise` executor is automatically converted to reject.
- Thinking Promises are "lazy" β they start running as soon as they're created.
FAQ
Q1. Can resolve/reject be called immediately?
Yes β but then-callbacks still run asynchronously.
Q2. Difference between catch and the second argument of then?
`then(onFul, onRej)`'s `onRej` does not catch errors thrown inside `onFul` on the same `then`. `catch` is safer.
Q3. What if I return a Promise inside another Promise?
It unwraps automatically β the next `then` receives the inner value.
Practice
- A `delay(ms)` Promise wrapper around setTimeout.
- Use Promise.all to run several tasks in parallel.
homework/README.md
## 1. delay β Write `delay(ms)` that returns a Promise resolving after `ms` milliseconds.
## 2. Parallel sum β Take three async numbers via Promise.all and print their sum.
See the `answer/` directory for solutions.
homework/answer/homework_01.js
/**
* A Promise that resolves after the given delay.
* @param {number} ms milliseconds to wait
* @returns {Promise<void>}
*/
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
console.log("start");
delay(500).then(() => console.log("500ms elapsed"));
homework/answer/homework_02.js
/**
* Fetch three async numbers in parallel and print the sum.
*/
function asyncNumber(n, ms) {
return new Promise((resolve) => setTimeout(() => resolve(n), ms));
}
Promise.all([asyncNumber(10, 100), asyncNumber(20, 150), asyncNumber(30, 80)])
.then((nums) => {
const sum = nums.reduce((a, b) => a + b, 0);
console.log("sum:", sum);
});
Next Lecture
[16_async_await](../16_async_await/) β Cleaner syntax for Promises.
All lecture materials and example code are openly available on GitHub.
View on GitHub β