A Promise is an object representing the eventual completion (or failure) of an asynchronous operation. It exists in one of three states: pending (initial), fulfilled (succeeded), or rejected (failed) — and once settled (fulfilled/rejected), its state never changes again.
const promise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
resolve("Data loaded");
} else {
reject(new Error("Failed to load"));
}
}, 1000);
});
promise
.then((result) => console.log(result)) // runs if resolved
.catch((error) => console.error(error)) // runs if rejected
.finally(() => console.log("Done")); // always runs, regardless of outcome
Each .then() returns a new promise, which lets you chain async steps sequentially instead of nesting callbacks (“callback hell”).
fetch("/api/user/1")
.then((res) => res.json())
.then((user) => fetch(`/api/posts?user=${user.id}`))
.then((res) => res.json())
.then((posts) => console.log(posts))
.catch((err) => console.error("Something in the chain failed:", err));
// a single .catch() at the end handles errors from any step above it
async/awaitSyntactic sugar over promises — lets asynchronous code read like synchronous code. An async function always returns a promise, and await pauses execution until that promise settles.
async function loadUserPosts(id) {
try {
const userRes = await fetch(`/api/user/${id}`);
const user = await userRes.json();
const postsRes = await fetch(`/api/posts?user=${user.id}`);
const posts = await postsRes.json();
return posts;
} catch (err) {
console.error("Something in the chain failed:", err);
}
}
Promise.all()Runs promises in parallel; resolves when all succeed, or rejects as soon as any one fails (fail-fast).
Promise.all([fetch("/a"), fetch("/b"), fetch("/c")])
.then((results) => console.log("All succeeded:", results))
.catch((err) => console.error("At least one failed:", err));
Promise.allSettled()Runs promises in parallel; always resolves once all have settled, giving you the outcome of each — never short-circuits on failure.
Promise.allSettled([
Promise.resolve(1),
Promise.reject("error"),
Promise.resolve(3)
]).then((results) => console.log(results));
// [
// { status: "fulfilled", value: 1 },
// { status: "rejected", reason: "error" },
// { status: "fulfilled", value: 3 }
// ]
Promise.race()Settles as soon as the first promise settles (whether fulfilled or rejected) — useful for timeouts.
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timed out")), ms)
);
return Promise.race([promise, timeout]);
}
withTimeout(fetch("/slow-endpoint"), 3000).catch((err) =>
console.error(err.message)
);
Promise.any()Resolves as soon as the first promise fulfills; only rejects if all of them reject (with an AggregateError).
Promise.any([
Promise.reject("fail 1"),
Promise.resolve("fail 2, but this one wins"),
Promise.reject("fail 3")
]).then(
(result) => console.log(result) // "fail 2, but this one wins"
);
// Sequential (slower) — each await blocks the next one from starting
async function sequential() {
const a = await fetch("/a"); // waits, then...
const b = await fetch("/b"); // waits, then...
return [a, b];
}
// Parallel (faster) — both requests fire immediately, then we wait together
async function parallel() {
const [a, b] = await Promise.all([fetch("/a"), fetch("/b")]);
return [a, b];
}