Promises
Promises represent a value that will be available in the future. They fix callback hell with chainable .then() and centralized .catch() error handling.
What is a Promise?
A Promise is an object representing the eventual completion or failure of an asynchronous operation. It's a placeholder for a future value.
A Promise is in one of three states:
- Pending โ initial state, operation not complete
- Fulfilled โ operation completed successfully (has a value)
- Rejected โ operation failed (has a reason/error)
const promise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("Operation succeeded!"); // fulfill with a value
} else {
reject(new Error("Something went wrong")); // reject with an error
}
});
promise
.then(value => console.log(value)) // "Operation succeeded!"
.catch(err => console.error(err));
Creating Promises
// Wrap a callback-based function in a Promise
function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
function fetchData(url) {
return new Promise((resolve, reject) => {
// Simulate network request
setTimeout(() => {
if (url.includes("valid")) {
resolve({ data: "some data" });
} else {
reject(new Error("Invalid URL"));
}
}, 500);
});
}
Chaining .then()
The key feature: each .then() returns a new Promise, enabling chaining:
function getUser(id) {
return Promise.resolve({ id, name: "Alice", teamId: 5 });
}
function getTeam(teamId) {
return Promise.resolve({ id: teamId, name: "Engineering" });
}
function getTeamMembers(teamId) {
return Promise.resolve(["Alice", "Bob", "Charlie"]);
}
// Clean chain โ no nesting!
getUser(1)
.then(user => {
console.log("User:", user.name);
return getTeam(user.teamId); // return a promise to chain
})
.then(team => {
console.log("Team:", team.name);
return getTeamMembers(team.id);
})
.then(members => {
console.log("Members:", members);
})
.catch(err => {
console.error("Something failed:", err.message);
});
๐กInfo
Whatever you return from a .then() callback becomes the resolved value for the next .then(). If you return a Promise, the chain waits for it to resolve.
Error Handling with .catch()
.catch() handles any rejection in the chain above it:
Promise.resolve("start")
.then(val => {
console.log(val); // "start"
throw new Error("Oops"); // throws an error
})
.then(val => {
console.log("Never runs");
})
.catch(err => {
console.error("Caught:", err.message); // "Caught: Oops"
return "recovered"; // can return to continue chain
})
.then(val => {
console.log("After recovery:", val); // "After recovery: recovered"
});
.finally() โ Cleanup
Runs after the Promise settles (fulfilled or rejected), great for cleanup:
let isLoading = true;
fetchData("valid-url")
.then(data => {
console.log("Got data:", data);
})
.catch(err => {
console.error("Error:", err.message);
})
.finally(() => {
isLoading = false; // always runs
console.log("Loading done. isLoading:", isLoading);
});
Static Promise Methods
Promise.resolve / Promise.reject
// Already-resolved/rejected promises
Promise.resolve(42).then(v => console.log(v)); // 42
Promise.reject(new Error("fail")).catch(e => console.error(e.message)); // "fail"
Promise.all โ All must succeed
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
Promise.all([p1, p2, p3])
.then(values => console.log(values)) // [1, 2, 3]
.catch(err => console.error("One failed:", err));
// If ANY promise rejects, the whole thing rejects
Practical use โ parallel API calls:
async function loadDashboard(userId) {
const [user, posts, followers] = await Promise.all([
fetchUser(userId),
fetchPosts(userId),
fetchFollowers(userId),
]);
return { user, posts, followers };
}
// All 3 requests run in parallel โ much faster than sequential!
Promise.allSettled โ All complete regardless
const promises = [
Promise.resolve("success"),
Promise.reject(new Error("fail")),
Promise.resolve("another success"),
];
Promise.allSettled(promises).then(results => {
results.forEach(result => {
if (result.status === "fulfilled") {
console.log("โ", result.value);
} else {
console.log("โ", result.reason.message);
}
});
});
// โ success
// โ fail
// โ another success
Promise.race โ First to settle wins
const fast = new Promise(resolve => setTimeout(() => resolve("fast"), 100));
const slow = new Promise(resolve => setTimeout(() => resolve("slow"), 500));
Promise.race([fast, slow]).then(result => {
console.log(result); // "fast"
});
// Timeout pattern
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timed out")), ms)
);
return Promise.race([promise, timeout]);
}
Promise.any โ First to succeed
Promise.any([
Promise.reject("fail 1"),
Promise.resolve("success"),
Promise.reject("fail 2"),
]).then(result => console.log(result)); // "success"
// Only rejects if ALL promises reject (AggregateError)
Converting Callbacks to Promises
// Manual wrapping
function readFilePromise(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, "utf8", (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
// Node's built-in promisify
const { promisify } = require("util");
const readFile = promisify(fs.readFile);
readFile("data.txt", "utf8").then(data => console.log(data));
Key Takeaways
- A Promise is in one of 3 states: pending, fulfilled, or rejected
.then()chains handle success;.catch()handles errors;.finally()always runs- Return a Promise from
.then()to chain asynchronous operations Promise.all()โ all succeed or fail together; great for parallel requestsPromise.allSettled()โ waits for all, regardless of success/failurePromise.race()โ first to settle wins (useful for timeouts)
Ready to test your knowledge?
Take a quiz on what you just learned.