Asynchronous JavaScript · Lesson 25 of 40
JavaScript Promises
A promise is an object representing a value that isn't available yet – the result of a network request, a file read or a timer. Instead of passing callbacks into a function, the function returns a promise, and you attach what should happen when it succeeds or fails. Promises are the foundation of async/await and of every modern web API.
In this lesson
- The problem: callback hell
- Promise states and creating a promise
then,catch,finallyand chaining- Running promises together:
all,allSettled,race,any
The problem promises solve
With callbacks, each dependent step nests inside the previous one, and every level needs its own error handling:
getUser(1, (err, user) => {
if (err) return showError(err);
getOrders(user.id, (err, orders) => {
if (err) return showError(err);
getInvoice(orders[0].id, (err, invoice) => {
if (err) return showError(err);
console.log(invoice); // the "pyramid of doom"
});
});
});
With promises the same flow is a flat chain with one error handler – and with await (next lesson) it reads like normal code.
States of a promise
A promise is always in one of three states
pending
- fulfilledresolve(value) was called → then callbacks run
- rejectedreject(error) or a throw → catch callbacks run
Once a promise settles (fulfilled or rejected) it never changes again.
Creating a promise
function checkStock(item, qty) {
return new Promise((resolve, reject) => {
setTimeout(() => { // pretend this is a server call
const available = { pen: 50, bag: 2 };
if (!(item in available)) reject(new Error(`Unknown item: ${item}`));
else if (available[item] < qty) reject(new Error(`Only ${available[item]} ${item}s left`));
else resolve({ item, qty, reserved: true });
}, 50);
});
}
checkStock("pen", 10)
.then((result) => console.log("OK:", result))
.catch((err) => console.log("Failed:", err.message));
checkStock("bag", 5)
.then((result) => console.log("OK:", result))
.catch((err) => console.log("Failed:", err.message))
.finally(() => console.log("Stock check finished"));
OK: { item: 'pen', qty: 10, reserved: true }
Failed: Only 2 bags left
Stock check finishedIn practice you rarely write new Promise yourself – fetch, database drivers and most libraries already return promises. You'll use it mainly to wrap old callback APIs.
Chaining
Each then returns a new promise resolved with whatever its callback returns. If the callback returns a promise, the chain waits for it:
const wait = (ms, value) => new Promise((r) => setTimeout(() => r(value), ms));
const getUser = (id) => wait(30, { id, name: "Asha" });
const getOrders = (user) => wait(30, [{ id: 101, total: 1200 }, { id: 102, total: 450 }]);
getUser(7)
.then((user) => {
console.log("User:", user.name);
return getOrders(user); // return the promise – the chain waits
})
.then((orders) => orders.reduce((sum, o) => sum + o.total, 0)) // return a value
.then((total) => console.log("Lifetime spend:", total))
.catch((err) => console.log("Something failed:", err.message));
User: Asha Lifetime spend: 1650
Common mistake: forgetting to return
If a then callback starts another async operation but doesn't return it, the next then runs immediately with undefined, and errors from that operation escape the chain's catch.
Error handling
Promise.resolve(10)
.then((n) => {
if (n > 5) throw new Error("Too big"); // a throw rejects the chain
return n;
})
.then(() => console.log("skipped")) // skipped after a rejection
.catch((err) => {
console.log("Caught:", err.message);
return 5; // recover with a fallback
})
.then((n) => console.log("Continuing with", n));
Caught: Too big Continuing with 5
A rejection skips every then until the nearest catch. Always end a chain with catch – an unhandled rejection is logged as an error in browsers and crashes a Node.js process.
Running promises together
const wait = (ms, value, fail = false) =>
new Promise((res, rej) => setTimeout(() => (fail ? rej(new Error(value)) : res(value)), ms));
const [profile, courses] = await Promise.all([wait(40, "profile"), wait(20, "courses")]);
console.log("all:", profile, courses);
try {
await Promise.all([wait(10, "ok"), wait(20, "server down", true)]);
} catch (e) {
console.log("all failed fast:", e.message);
}
const results = await Promise.allSettled([wait(10, "ok"), wait(20, "timeout", true)]);
console.log(results.map((r) => r.status === "fulfilled" ? r.value : `error: ${r.reason.message}`));
console.log("race:", await Promise.race([wait(50, "slow"), wait(10, "fast")]));
console.log("any:", await Promise.any([wait(10, "mirror A down", true), wait(30, "mirror B")]));
all: profile courses all failed fast: server down [ 'ok', 'error: timeout' ] race: fast any: mirror B
| Method | Resolves when | Rejects when | Use for |
|---|---|---|---|
Promise.all | all succeed (results in order) | any one fails | loading data you need all of |
Promise.allSettled | all have finished | never | independent jobs; report each result |
Promise.race | first to settle succeeds | first to settle fails | timeouts |
Promise.any | first success | all fail | fastest of several mirrors |
Practice
Try it yourself
Write
withTimeout(promise, ms)that rejects with "Timed out" if the promise takes longer thanms.Show solution
const wait = (ms, v) => new Promise((r) => setTimeout(() => r(v), ms)); function withTimeout(promise, ms) { let timer; const timeout = new Promise((_, reject) => { timer = setTimeout(() => reject(new Error("Timed out")), ms); }); return Promise.race([promise, timeout]).finally(() => clearTimeout(timer)); } withTimeout(wait(20, "fast data"), 100).then(console.log); withTimeout(wait(200, "slow data"), 50).catch((e) => console.log(e.message));fast data Timed out
Quick check
What are the three states of a promise?
Pending, fulfilled and rejected.
What does .then() return?
A new promise, resolved with the callback's return value (or following a returned promise).
Promise.all vs Promise.allSettled?
all rejects as soon as one fails; allSettled waits for all and reports each outcome.
Summary
- A promise is pending, then fulfilled or rejected – once.
- then returns a new promise; return values and promises to keep chains flat; end with catch.
- Combine with all, allSettled, race and any.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.