JSON in JavaScript
A Promise in JavaScript is an object that represents the eventual result of an asynchronous operation. Think of it like ordering food at a restaurant. When you order, the waiter gives you a receipt. You do not have the food yet, but you have a promise that it will arrive. You can go back to chatting while you wait, and when the food is ready, the waiter brings it to you.
Before Promises, developers handled asynchronous code using callbacks. As the number of operations grew, callbacks would nest inside other callbacks, creating deeply indented, hard-to-read code known as callback hell.
getUser(1, function(user) {
getOrders(user.id, function(orders) {
getOrderDetails(orders[0].id, function(details) {
// deeply nested and hard to follow
});
});
});
Promises replace this pattern with a clean, chainable approach. Instead of nesting, you chain .then() calls one after another.
getUser(1)
.then(user => getOrders(user.id))
.then(orders => getOrderDetails(orders[0].id))
.then(details => console.log(details))
.catch(error => console.error(error));
A Promise is created using the built-in Promise constructor and always produces one of three possible states.
Every Promise exists in exactly one of three states at any given moment:
- Pending - The initial state. The operation has not finished yet.
- Fulfilled - The operation completed successfully and the Promise has a result value.
- Rejected - The operation failed and the Promise has a reason (an error).
Once a Promise moves from pending to either fulfilled or rejected, it is settled and its state can never change again.
// A Promise starts as "pending"
const myPromise = new Promise((resolve, reject) => {
// ... async work happens here
});
// After calling resolve() it becomes "fulfilled"
// After calling reject() it becomes "rejected"
// Once settled, the state is locked forever
You can think of the three states as a traffic light. Red is pending (waiting). Green is fulfilled (success). Red again is rejected (failure). The light only changes once.
const p = new Promise((resolve, reject) => {
resolve("Success!"); // moves to fulfilled
reject("This is ignored"); // too late, already settled
});
p.then(value => console.log(value)); // "Success!"
You create a Promise using the new Promise() constructor. It takes a single function called the executor function. The executor runs immediately and receives two arguments: resolve and reject. You call one of them when the async work completes.
const myPromise = new Promise(function(resolve, reject) {
// Do async work here
const success = true;
if (success) {
resolve("The operation worked!");
} else {
reject("Something went wrong.");
}
});
In real-world code, you often wrap async operations like setTimeout, network requests, or file reads inside a Promise.
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ id: 1, name: "Alice" }); // simulate a server response
}, 1000);
});
}
fetchData().then(data => console.log(data));
// After 1 second: { id: 1, name: "Alice" }
You can also use the shorthand static methods Promise.resolve() and Promise.reject() to create already-settled Promises instantly. These are useful in tests or when you want to return a value that already looks like a Promise.
const resolved = Promise.resolve(42);
resolved.then(v => console.log(v)); // 42
const rejected = Promise.reject(new Error("Oops!"));
rejected.catch(e => console.error(e.message)); // Oops!
Once you have a Promise, you react to its result using:
.then(onFulfilled)- runs when the Promise is fulfilled. Receives the resolved value..catch(onRejected)- runs when the Promise is rejected. Receives the rejection reason (usually an error)..finally(onFinally)- runs after the Promise settles regardless of success or failure. Useful for cleanup.
function divide(a, b) {
return new Promise((resolve, reject) => {
if (b === 0) {
reject(new Error("Cannot divide by zero"));
} else {
resolve(a / b);
}
});
}
divide(10, 2)
.then(result => console.log("Result:", result)) // Result: 5
.catch(error => console.error("Error:", error.message));
divide(10, 0)
.then(result => console.log("Result:", result)) // skipped
.catch(error => console.error("Error:", error.message)); // Error: Cannot divide by zero
function loadData() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve("Data loaded"), 500);
});
}
loadData()
.then(data => console.log(data))
.catch(err => console.error(err))
.finally(() => console.log("Loading complete")); // always runs
An important detail: .then() itself returns a new Promise. This is what makes chaining possible.
Because .then() always returns a new Promise, you can chain multiple .then() calls to run async steps one after another. The value returned from one .then() callback becomes the input to the next.
function getUser(id) {
return Promise.resolve({ id: id, name: "Alice" });
}
function getScore(user) {
return Promise.resolve({ user: user.name, score: 95 });
}
function displayResult(data) {
console.log(`${data.user} scored ${data.score}`);
}
getUser(1)
.then(user => getScore(user)) // returns a new Promise
.then(data => displayResult(data)) // runs after getScore settles
.catch(error => console.error(error));
// Alice scored 95
You only need one .catch() at the end of the chain. If any step throws an error or returns a rejected Promise, the chain skips all remaining .then() handlers and jumps straight to .catch().
Promise.resolve(10)
.then(n => n * 2) // 20
.then(n => { throw new Error("Oops!"); }) // triggers error
.then(n => n + 1) // SKIPPED
.catch(err => console.error(err.message)); // "Oops!"
Keep your chains flat. Avoid returning a .then() inside another .then() callback unnecessarily as that brings back the nesting problem Promises were designed to solve.
Promise.all() takes an array of Promises and returns a single new Promise. That new Promise fulfills when all of the input Promises have fulfilled. It rejects immediately if any one of them rejects.
const p1 = Promise.resolve("User data");
const p2 = Promise.resolve("Order data");
const p3 = Promise.resolve("Product data");
Promise.all([p1, p2, p3])
.then(results => {
console.log(results);
// ["User data", "Order data", "Product data"]
})
.catch(error => console.error("One failed:", error));
The resolved value is an array of results in the same order as the input array, regardless of which Promise resolved first. This makes it easy to match results to their source.
const fast = Promise.resolve("Fast");
const failing = Promise.reject(new Error("Network error"));
const slow = Promise.resolve("Slow");
Promise.all([fast, failing, slow])
.then(results => console.log(results)) // SKIPPED
.catch(error => console.error(error.message)); // "Network error"
Use Promise.all() when all operations must succeed and you want to run them in parallel rather than waiting for each one to complete before starting the next.
Promise.allSettled() also takes an array of Promises, but unlike Promise.all(), it never rejects. It always waits for every Promise to settle (fulfilled or rejected) and then returns an array of result objects describing each outcome.
Each result object has:
{ status: "fulfilled", value: ... }for successful Promises{ status: "rejected", reason: ... }for failed Promises
const p1 = Promise.resolve("Success A");
const p2 = Promise.reject(new Error("Failed B"));
const p3 = Promise.resolve("Success C");
Promise.allSettled([p1, p2, p3]).then(results => {
results.forEach(result => {
if (result.status === "fulfilled") {
console.log("OK:", result.value);
} else {
console.log("FAIL:", result.reason.message);
}
});
});
// OK: Success A
// FAIL: Failed B
// OK: Success C
Use Promise.allSettled() when you want to run multiple operations in parallel and need to handle every result individually, even if some fail.
Promise.race() takes an array of Promises and returns a new Promise that settles as soon as the first Promise in the array settles, whether fulfilled or rejected.
const slow = new Promise(resolve => setTimeout(() => resolve("Slow"), 2000));
const fast = new Promise(resolve => setTimeout(() => resolve("Fast"), 500));
const medium = new Promise(resolve => setTimeout(() => resolve("Medium"), 1000));
Promise.race([slow, fast, medium])
.then(winner => console.log(winner)); // "Fast" (resolves first)
A practical use case is implementing timeouts. You race your actual operation against a timeout Promise. If the timeout settles first, you treat the operation as failed.
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timed out after ${ms}ms`)), ms)
);
return Promise.race([promise, timeout]);
}
const fetchSomething = new Promise(resolve =>
setTimeout(() => resolve("Data"), 3000)
);
withTimeout(fetchSomething, 1000)
.then(data => console.log(data))
.catch(err => console.error(err.message)); // "Timed out after 1000ms"
Note: the other Promises in the race still run to completion. They are just ignored once the winner settles.
Promise.any(), which resolves as soon as the first Promise fulfills (ignoring rejections). It only rejects if every Promise in the array rejects.
Here is a quick reference for everything covered in this tutorial:
| Concept | What it does |
|---|---|
new Promise(executor) |
Creates a new Promise; executor receives resolve and reject |
| Pending | Initial state; the async work is still in progress |
| Fulfilled | Operation succeeded; resolve(value) was called |
| Rejected | Operation failed; reject(reason) was called |
.then() |
Handles a fulfilled Promise; returns a new Promise (enables chaining) |
.catch() |
Handles a rejected Promise; catches errors from the whole chain |
.finally() |
Runs after the Promise settles, regardless of outcome |
Promise.resolve(v) |
Returns an already-fulfilled Promise with value v |
Promise.reject(r) |
Returns an already-rejected Promise with reason r |
Promise.all(arr) |
Fulfills when all input Promises fulfill; rejects if any rejects |
Promise.allSettled(arr) |
Always fulfills; returns an array of outcome objects for every Promise |
Promise.race(arr) |
Settles as soon as the first input Promise settles |
Promise.any(arr) |
Fulfills with the first successful result; rejects only if all reject |
Q: What is a Promise in JavaScript?
A Promise is an object that represents the eventual result of an asynchronous operation. It has three states: pending, fulfilled, and rejected. Once settled, its state cannot change.
Q: What is the difference between Promise.all() and Promise.allSettled()?
Promise.all() rejects immediately if any single Promise rejects and you only get successful results. Promise.allSettled() always waits for every Promise and gives you the outcome (fulfilled or rejected) for each one, regardless of failures.
Q: What does .then() return?
.then() always returns a new Promise. Whatever value you return from the .then() callback becomes the fulfilled value of that new Promise, which allows you to chain multiple .then() calls together.
Q: How do you handle errors in a Promise chain?
You add a single .catch() at the end of the chain. If any step in the chain throws an error or returns a rejected Promise, all subsequent .then() handlers are skipped and the .catch() handler receives the error.
Q: What is the difference between Promise.race() and Promise.any()?
Promise.race() settles with the first Promise to settle, whether fulfilled or rejected. Promise.any() only fulfills with the first successful result; it ignores rejections unless every Promise rejects, in which case it rejects with an AggregateError.
Q: What is callback hell and how do Promises solve it?
Callback hell refers to deeply nested callback functions that make code hard to read and maintain. Promises solve it by providing a chainable API where each step is written at the same indentation level using .then(), making the async flow read from top to bottom like synchronous code.
Q: When would you use Promise.allSettled() over Promise.all()?
Use Promise.allSettled() when you need results from all operations even if some fail, for example when sending multiple independent API requests where partial success is acceptable. Use Promise.all() when all requests must succeed for the operation to be meaningful.