Imagine you order a pizza. You don't get it immediately, you get a receipt. That receipt is a promiseWhat is promise?An object that represents a value you don't have yet but will get in the future, letting your code keep running while it waits.: you will receive a pizza in the future. While you wait, you do other things. When the pizza arrives, you eat it. If the oven breaks, you handle that situation. JavaScript Promises work exactly this way. They represent operations that haven't completed yet but will eventually produce a result or an error.
Why we need promises
JavaScript is single-threadedWhat is single-threaded?A model where one main execution thread handles all work - Node.js uses this with an event loop to handle many requests concurrently.. Without Promises, fetching data from a server would freeze your entire application until the response arrives. Promises let JavaScript say "go fetch the data, let me know when you're done" and continue running other code in the meantime.
The three states
Every PromiseWhat is promise?An object that represents a value you don't have yet but will get in the future, letting your code keep running while it waits. exists in exactly one of three states:
| State | Description | What happens next |
|---|---|---|
| Pending | Initial state, operation ongoing | Waiting for resolve or reject |
| Fulfilled | Operation succeeded | .then() callback runs |
| Rejected | Operation failed | .catch() callback runs |
Once a Promise moves from pending to fulfilled or rejected, it stays that way permanently. This immutabilityWhat is immutability?A coding practice where you never change existing data directly, but instead create a new copy with the changes applied. makes Promises predictable.
Creating a promiseWhat is promise?An object that represents a value you don't have yet but will get in the future, letting your code keep running while it waits.
The Promise constructor takes a function with two parameters: resolve (call on success) and reject (call on failure).
const pizzaPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const ovenWorking = true;
if (ovenWorking) {
resolve({ type: 'Pepperoni', size: 'Large' });
} else {
reject(new Error('Oven malfunction'));
}
}, 2000);
});.catch(). Without a catch handler, a rejected promise becomes an unhandled rejection, which silently fails in browsers and crashes the process in newer versions of Node.js. Every promise chain needs a .catch() at the end, no exceptions.Consuming promises with .then() and .catch()
pizzaPromise
.then(pizza => {
console.log(`Your ${pizza.size} ${pizza.type} is ready!`);
return pizza;
})
.catch(error => {
console.error('Order failed:', error.message);
});Chaining promises
Each .then() can return a value or a new PromiseWhat is promise?An object that represents a value you don't have yet but will get in the future, letting your code keep running while it waits., creating a pipelineWhat is pipeline?A sequence of automated steps (install, lint, test, build, deploy) that code passes through before reaching production.:
fetchUser(123)
.then(user => fetchUserOrders(user.id))
.then(orders => calculateTotal(orders))
.then(total => console.log(`Total: $${total}`))
.catch(error => console.error('Failed:', error.message));Compare this to the old callbackWhat is callback?A function you pass into another function to be called later, often when an operation finishes or an event occurs. nesting ("callback hell"):
// Callback hell - avoid this
fetchUser(123, (user) => {
fetchUserOrders(user.id, (orders) => {
calculateTotal(orders, (total) => {
console.log(`Total: $${total}`);
});
});
});Handling multiple promises
| Method | Resolves when | Rejects when | Use case |
|---|---|---|---|
Promise.all() | All succeed | Any one fails | Loading multiple required resources |
Promise.race() | First settles | First settles as rejection | Timeouts, redundant requests |
Promise.allSettled() | All complete | Never | Handling partial failures |
Promise.any() | First succeeds | All fail | Fastest successful response |
// Wait for all - fails fast if any rejects
const [user, posts, comments] = await Promise.all([
fetchUser(1),
fetchPosts(),
fetchComments()
]);
// Wait for all regardless of success/failure
const results = await Promise.allSettled([
fetchUser(1),
fetchUser(2),
fetchUser(3)
]);
results.forEach((result, i) => {
if (result.status === 'fulfilled') {
console.log(`User ${i + 1}:`, result.value);
} else {
console.error(`User ${i + 1} failed:`, result.reason);
}
});Common mistakes
Forgetting to return in .then():
// Broken - undefined passes to the next .then()
fetchUser()
.then(user => {
fetchOrders(user.id); // Missing return!
})
.then(orders => console.log(orders)); // undefined
// Fixed - return the Promise
fetchUser()
.then(user => fetchOrders(user.id))
.then(orders => console.log(orders));Creating unnecessary Promises:
// Unnecessary wrapper
const promise = new Promise(resolve => resolve(42));
// Use Promise.resolve() for existing values
const promise = Promise.resolve(42);Quick reference
| Pattern | Syntax |
|---|---|
| Create | new Promise((resolve, reject) => { ... }) |
| Handle success | .then(value => { ... }) |
| Handle error | .catch(error => { ... }) |
| Cleanup | .finally(() => { ... }) |
| Wrap value | Promise.resolve(value) |
| Wrap error | Promise.reject(error) |
| All succeed | Promise.all([p1, p2]) |
| First to settle | Promise.race([p1, p2]) |
| All outcomes | Promise.allSettled([p1, p2]) |
const fetchUser = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ name: "Alice", age: 25 });
}, 1000);
});
fetchUser
.then(user => console.log(user.name))
.catch(err => console.error(err));