Promises
A promise is an object for a value that will be ready later — or an error. Promises fix callback hell, compose well, and are what async/await is built on.
On this page · 5 min · Intermediate
On this page
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 5 min
- Difficulty
- Intermediate
- Updated
- Oct 28, 2026
Related tutorials
Learning objectives#
- Create promises and consume them with then/catch/finally
- Chain dependent steps
- Use promise-based core modules
- Handle unhandled rejections
Explanation#
A promise is pending, then either fulfilled (with a value) or rejected (with an error) — once.
Create one with new Promise((resolve, reject) => ...). Usually you get promises from APIs instead: fs/promises, fetch, database drivers.
then(fn) receives the value and returns a new promise, so you can chain steps; catch(fn) handles errors from any earlier step; finally(fn) runs either way (close connections, hide spinners).
Combinators: Promise.all (all succeed), allSettled (all finish), race (first to settle), any (first to succeed).
An unhandled rejection crashes Node by default. Always end chains with catch (or use try/catch with await), and log process "unhandledRejection" events as a safety net.
Syntax#
getUser(7)
.then(user => getOrders(user.id))
.then(orders => console.log(orders.length))
.catch(err => console.error(err.message))
.finally(() => db.close());
Code example: Creating, chaining and combining promises#
import { setTimeout as delay } from 'node:timers/promises';
function checkStock(product, qty) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (qty <= product.stock) resolve({ ...product, reserved: qty });
else reject(new Error(`Only ${product.stock} ${product.name} left`));
}, 30);
});
}
const pen = { name: 'Pen', price: 20, stock: 10 };
// Chain: each then receives the previous result
checkStock(pen, 3)
.then(item => item.price * item.reserved)
.then(total => console.log('Total:', total))
.catch(err => console.log('Failed:', err.message))
.finally(() => console.log('Stock check 1 finished'));
await delay(50);
checkStock(pen, 30)
.then(item => console.log('Reserved', item.reserved))
.catch(err => console.log('Failed:', err.message))
.finally(() => console.log('Stock check 2 finished'));
await delay(50);
// Combinators
const fast = delay(10, 'fast mirror');
const slow = delay(100, 'slow mirror');
console.log('race →', await Promise.race([fast, slow]));
console.log('any →', await Promise.any([Promise.reject(new Error('down')), delay(20, 'backup')]));
Total: 60 Stock check 1 finished Failed: Only 10 Pen left Stock check 2 finished race → fast mirror any → backup
This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- checkStock wraps a timer in new Promise, resolving or rejecting once.
- The first chain computes the total in a then and prints it; finally runs at the end.
- The second check rejects, so the then is skipped and catch handles the error.
- timers/promises provides a promise-based setTimeout (delay).
- race returns the first to settle; any returns the first to succeed, ignoring rejections.
Real-world use case#
A checkout reserves stock, charges the card and creates the order as a promise chain; finally always releases the database connection, whether the checkout succeeded or failed.
Common mistakes#
then(user => { getOrders(user.id); }) passes undefined to the next step. Return the promise.
An unhandled rejection crashes the process in modern Node. Every chain needs error handling.
new Promise(resolve => fetch(url).then(resolve)) is the “explicit construction” anti-pattern and loses errors. Return the existing promise.
Best practices#
- Return promises from then callbacks
- End every chain with catch
- Use finally for cleanup
- Prefer promise-based core modules
Remember this#
pending → fulfilled or rejected (once).
then chains, catch handles, finally cleans up.
Unhandled rejection = crash.
Practice exercise#
Your task: Write a sleep(ms) function that returns a promise, then log "A", wait 100 ms, log "B".
Need a hint?
Show solution
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
console.log('A');
await sleep(100);
console.log('B');
Mini quiz#
Which method runs whether the promise was fulfilled or rejected?
Want more? Practise all Node.js MCQs · Node.js interview questions
My notes#
Saved in this browser.