Docs
README
9.2 Promises
Introduction
A Promise is an object representing the eventual completion or failure of an asynchronous operation. Promises provide a cleaner alternative to callbacks for handling async code.
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā PROMISE STATES ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā āāāāāāāāāāāāāāāā ā
ā ā PENDING ā ā
ā ā (initial) ā ā
ā āāāāāāāā¬āāāāāāāā ā
ā ā ā
ā āāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāā ā
ā ā ā ā
ā ā¼ ā¼ ā
ā āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā ā
ā ā FULFILLED ā ā REJECTED ā ā
ā ā (resolved) ā ā (error) ā ā
ā āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā ā
ā ā
ā ⢠A promise is settled (fulfilled or rejected) only once ā
ā ⢠Once settled, the state cannot change ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Creating Promises
The Promise Constructor
const promise = new Promise((resolve, reject) => {
// Async operation
const success = true;
if (success) {
resolve('Operation completed!'); // Fulfill the promise
} else {
reject(new Error('Operation failed!')); // Reject the promise
}
});
Basic Example
function fetchData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url) {
resolve({ data: 'Sample data', url });
} else {
reject(new Error('URL is required'));
}
}, 1000);
});
}
// Using the promise
fetchData('/api/users')
.then((result) => console.log(result))
.catch((error) => console.error(error));
Consuming Promises
.then() Method
promise
.then((value) => {
// Handle fulfilled state
console.log('Success:', value);
return value.toUpperCase(); // Can return new value
})
.then((upperValue) => {
// Chained - receives previous return value
console.log('Uppercase:', upperValue);
});
.catch() Method
promise
.then((value) => {
console.log(value);
throw new Error('Something went wrong!');
})
.catch((error) => {
// Handles any error in the chain
console.error('Error:', error.message);
});
.finally() Method
promise
.then((result) => console.log(result))
.catch((error) => console.error(error))
.finally(() => {
// Always runs, regardless of outcome
console.log('Cleanup complete');
});
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā PROMISE METHODS ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā promise ā
ā .then(onFulfilled) // Handle success ā
ā .then(onFulfilled, onRejected) // Both handlers ā
ā .catch(onRejected) // Handle errors ā
ā .finally(onFinally) // Always runs ā
ā ā
ā Chain Flow: ā
ā āāāāāāāāā āāāāāāāāā āāāāāāāāā āāāāāāāāāāā ā
ā ā then āāāāāŗā then āāāāāŗā catch āāāāāŗā finally ā ā
ā āāāāāāāāā āāāāāāāāā āāāāāāāāā āāāāāāāāāāā ā
ā ā ā ā² ā
ā ā āāāāāāāāāāāāāā ā
ā ā (error propagates) ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāŗ ā
ā (value passes through) ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Promise Chaining
Sequential Operations
// ā
Clean chaining - solves callback hell!
getUser(userId)
.then((user) => {
console.log('User:', user);
return getOrders(user.id);
})
.then((orders) => {
console.log('Orders:', orders);
return getOrderDetails(orders[0].id);
})
.then((details) => {
console.log('Details:', details);
})
.catch((error) => {
// Single error handler for entire chain
console.error('Error:', error);
});
Returning Values
Promise.resolve(5)
.then((x) => x * 2) // Returns 10
.then((x) => x + 3) // Returns 13
.then((x) => console.log(x)); // Logs: 13
Returning Promises
Promise.resolve('start')
.then((value) => {
return new Promise((resolve) => {
setTimeout(() => resolve(value + ' ā step1'), 100);
});
})
.then((value) => {
return new Promise((resolve) => {
setTimeout(() => resolve(value + ' ā step2'), 100);
});
})
.then((value) => console.log(value));
// Logs: "start ā step1 ā step2"
Static Methods
Promise.resolve() and Promise.reject()
// Create resolved promise
const resolved = Promise.resolve('immediate value');
resolved.then((v) => console.log(v)); // "immediate value"
// Create rejected promise
const rejected = Promise.reject(new Error('immediate error'));
rejected.catch((e) => console.error(e.message)); // "immediate error"
Promise.all()
Waits for all promises to fulfill (or any to reject):
const promises = [
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments'),
];
Promise.all(promises)
.then(([users, posts, comments]) => {
console.log('All data loaded!');
})
.catch((error) => {
console.error('One or more requests failed:', error);
});
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Promise.all() ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā Promise 1 āāāāāāāāāāāāāŗ ā ā
ā Promise 2 āāāāāāāāāāāāāāāāāāāāāŗ ā āāāāŗ [R1, R2, R3] ā
ā Promise 3 āāāāāāāāāŗ ā All results ā
ā ā
ā BUT if any fails: ā
ā ā
ā Promise 1 āāāāāāāāāāāāāŗ ā ā
ā Promise 2 āāāāāāāāāāāāāāāāāāāāāŗ ā āāāāŗ Error ā
ā Promise 3 āāāāāāāāāŗ ā Fast-fail ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Promise.allSettled()
Waits for all promises to settle (no short-circuit):
const promises = [
Promise.resolve(1),
Promise.reject(new Error('fail')),
Promise.resolve(3),
];
Promise.allSettled(promises).then((results) => {
console.log(results);
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: Error: fail },
// { status: 'fulfilled', value: 3 }
// ]
});
Promise.race()
Returns first settled promise (fulfilled or rejected):
const promises = [
new Promise((r) => setTimeout(() => r('slow'), 500)),
new Promise((r) => setTimeout(() => r('fast'), 100)),
];
Promise.race(promises).then((result) => {
console.log(result); // "fast"
});
Promise.any()
Returns first fulfilled promise (ignores rejections):
const promises = [
Promise.reject(new Error('Error 1')),
new Promise((r) => setTimeout(() => r('Success!'), 100)),
Promise.reject(new Error('Error 2')),
];
Promise.any(promises)
.then((result) => console.log(result)) // "Success!"
.catch((error) => console.error(error)); // AggregateError if all reject
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā STATIC METHODS COMPARISON ā
āāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Method ā Behavior ā
āāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Promise.all ā All fulfill ā array of results ā
ā ā Any reject ā first rejection ā
āāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Promise.allSettled ā Wait for all ā array of outcomes ā
ā ā Never short-circuits ā
āāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Promise.race ā First to settle (fulfilled or rejected) ā
āāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Promise.any ā First to fulfill ā
ā ā All reject ā AggregateError ā
āāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Error Handling
Error Propagation
Promise.resolve()
.then(() => {
throw new Error('Error in step 1');
})
.then(() => {
console.log("This won't run");
})
.then(() => {
console.log("This won't run either");
})
.catch((error) => {
console.error('Caught:', error.message);
// Error handled, chain continues
return 'recovered';
})
.then((value) => {
console.log('Continued with:', value); // "recovered"
});
Re-throwing Errors
promise
.catch((error) => {
if (error.code === 'RETRY') {
return retryOperation(); // Handle specific error
}
throw error; // Re-throw others
})
.catch((error) => {
console.error('Final error handler:', error);
});
Multiple Catch Blocks
fetchUser(id)
.then((user) => validateUser(user))
.catch((error) => {
// Handle validation errors
console.log('Validation failed, using default');
return defaultUser;
})
.then((user) => saveUser(user))
.catch((error) => {
// Handle save errors
console.error('Failed to save:', error);
});
Common Patterns
Converting Callbacks to Promises
// Callback-based function
function readFileCallback(path, callback) {
// ... callback(error, data)
}
// Promise wrapper
function readFile(path) {
return new Promise((resolve, reject) => {
readFileCallback(path, (error, data) => {
if (error) reject(error);
else resolve(data);
});
});
}
// Node.js util.promisify
const { promisify } = require('util');
const readFilePromise = promisify(readFileCallback);
Timeout Pattern
function timeout(ms) {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error('Timeout')), ms);
});
}
function fetchWithTimeout(url, ms) {
return Promise.race([fetch(url), timeout(ms)]);
}
fetchWithTimeout('/api/data', 5000)
.then((response) => response.json())
.catch((error) => console.error(error.message));
Retry Pattern
function retry(fn, attempts = 3, delay = 1000) {
return fn().catch((error) => {
if (attempts <= 1) throw error;
return new Promise((r) => setTimeout(r, delay)).then(() =>
retry(fn, attempts - 1, delay)
);
});
}
retry(() => fetch('/api/flaky-endpoint'), 3, 1000)
.then((response) => console.log('Success!'))
.catch((error) => console.error('Failed after 3 attempts'));
Sequential Promise Execution
// Process array items sequentially
function sequential(items, asyncFn) {
return items.reduce((promise, item) => {
return promise.then((results) => {
return asyncFn(item).then((result) => [...results, result]);
});
}, Promise.resolve([]));
}
// Usage
const urls = ['/api/1', '/api/2', '/api/3'];
sequential(urls, (url) => fetch(url).then((r) => r.json())).then((results) =>
console.log(results)
);
Promise vs Callback Comparison
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā CALLBACKS vs PROMISES ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā CALLBACKS PROMISES ā
ā āāāāāāāāā āāāāāāāā ā
ā ā
ā getUser(id, (err, user) => { getUser(id) ā
ā if (err) handleError(err); .then(user => ā
ā getOrders(user.id, (err, getOrders(user.id)) ā
ā orders) => { .then(orders => ā
ā if (err) handleError(err); getDetails( ā
ā getDetails(orders[0].id, orders[0].id)) ā
ā (err, details) => { .then(details => ā
ā if (err) handleError(err); console.log( ā
ā console.log(details); details)) ā
ā }); .catch(handleError); ā
ā }); ā
ā }); ā
ā ā
ā ā Nested ā
Flat ā
ā ā Error handling repeated ā
Single catch ā
ā ā Hard to compose ā
Chainable ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Common Mistakes
1. Forgetting to Return
// ā Wrong - promise not returned
promise
.then((value) => {
anotherPromise(value); // Not returned!
})
.then((result) => {
console.log(result); // undefined!
});
// ā
Correct
promise
.then((value) => {
return anotherPromise(value);
})
.then((result) => {
console.log(result); // Actual result
});
2. Nested .then() (Promise Hell)
// ā Wrong - recreating callback hell
promise.then((a) => {
return promiseB(a).then((b) => {
return promiseC(b).then((c) => {
return c;
});
});
});
// ā
Correct - flat chain
promise
.then((a) => promiseB(a))
.then((b) => promiseC(b))
.then((c) => c);
3. Not Handling Rejections
// ā Unhandled rejection
promise.then((result) => console.log(result));
// If promise rejects, error is swallowed!
// ā
Always add .catch()
promise
.then((result) => console.log(result))
.catch((error) => console.error(error));
Summary
| Feature | Description |
|---|---|
new Promise() | Create a new promise |
.then() | Handle fulfilled value |
.catch() | Handle rejection |
.finally() | Always execute |
Promise.resolve() | Create fulfilled promise |
Promise.reject() | Create rejected promise |
Promise.all() | Wait for all (fail-fast) |
Promise.allSettled() | Wait for all (no fail-fast) |
Promise.race() | First to settle |
Promise.any() | First to fulfill |
What's Next?
In the next section, we'll learn about async/await - syntactic sugar that makes promises even easier to work with:
// Preview: async/await
async function loadData() {
try {
const user = await getUser(id);
const orders = await getOrders(user.id);
const details = await getDetails(orders[0].id);
console.log(details);
} catch (error) {
console.error(error);
}
}