A Promise represents a value that isn't ready yet, but will be — either successfully (resolved) or unsuccessfully (rejected). Where a callback hands control over immediately, a Promise gives you back an object you can attach .then() and .catch() handlers to, and — importantly — chain together cleanly.
Creating and using a basic Promise
function checkStock(itemId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const inStock = itemId !== 'sold-out-item';
if (inStock) {
resolve({ itemId, available: true });
} else {
reject(new Error('Item is out of stock'));
}
}, 300);
});
}
checkStock('item-42')
.then((result) => console.log('Available:', result))
.catch((error) => console.error('Failed:', error.message));
Where chaining earns its name: sequencing dependent steps
Recall the nested callback example from the previous page. Written with Promises, the same sequence flattens out instead of drifting rightward:
getUser(id)
.then((user) => getOrders(user.id))
.then((orders) => getInvoice(orders[0].id))
.then((invoice) => sendEmail(invoice.customerEmail, invoice))
.then(() => console.log('Email sent successfully'))
.catch((error) => console.error('Something failed along the way:', error.message));
Each .then() receives whatever the previous one returned, and returning a new Promise from inside a .then() makes the chain wait for it before moving to the next step. One .catch() at the very end handles an error from any step in the chain — no need to repeat error-handling logic four times.
Running independent tasks in parallel
When steps don't depend on each other, running them one after another wastes time. Promise.all() runs them simultaneously and waits for all to finish:
Promise.all([
fetchWeather('Chattogram'),
fetchExchangeRate('USD', 'BDT'),
fetchNewsHeadlines()
])
.then(([weather, rate, headlines]) => {
console.log(weather, rate, headlines);
})
.catch((error) => console.error('One of the requests failed:', error.message));
If even one of the three rejects, the whole Promise.all() rejects immediately — useful when you genuinely need every result, but worth knowing if you'd rather get partial results instead (in which case Promise.allSettled() is the better fit).
A quick comparison with plain callbacks
| Callbacks | Promises | |
|---|---|---|
| Sequencing steps | Nested, drifts right | Flat .then() chain |
| Error handling | Repeated in every callback | One .catch() covers the chain |
| Running tasks in parallel | Manual bookkeeping | Promise.all() |
Promises don't replace callbacks entirely — plenty of core Node.js APIs are still callback-based — but for your own async logic, especially anything with multiple dependent steps, Promises (and the async/await syntax built on top of them) are almost always the more maintainable choice.