
Promises are a fundamental JavaScript feature for handling asynchronous operations, providing a cleaner alternative to callback hell and better error handling. They help manage asynchronous code execution, improving the readability and maintainability of applications. By enabling chaining and handling errors more effectively, promises streamline complex workflows and enhance overall performance.
1. Promise Basics
States:
- Pending: The initial state, neither fulfilled nor rejected.
- Fulfilled: Indicates that the operation completed successfully.
- Rejected: Indicates that the operation failed.
Creating a Promise:
const myPromise = new Promise((resolve, reject) => {
// Async operation
const success = true;
if(success) {
resolve('Operation succeeded!');
} else {
reject('Operation failed!');
}
});
2. Consuming Promises
.then() / .catch() / .finally()
myPromise
.then(result => {
console.log(result); // "Operation succeeded!"
})
.catch(error => {
console.error(error);
})
.finally(() => {
console.log('Cleanup here');
});
Promise Chaining:
fetchData()
.then(processData)
.then(saveData)
.catch(handleError);
3. Async/Await Syntax
Basic Usage:
async function fetchUser() {
try {
const response = await fetch('/api/user');
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch failed:', error);
}
}
Error Handling:
async function main() {
try {
const result = await riskyOperation();
} catch (err) {
// Handle errors from any await call
}
}
4. Promise Combinators
Promise.all()
const [users, posts] = await Promise.all([
fetch('/users'),
fetch('/posts')
]);
Promise.race()
const timeout = new Promise((_, reject) =>
setTimeout(() => reject('Timeout'), 5000));
Promise.race([fetchData(), timeout])
.then(data => console.log(data))
.catch(err => console.error(err));
Promise.allSettled()
const results = await Promise.allSettled([
successPromise(),
failedPromise()
]);
// [
// {status: "fulfilled", value: 42},
// {status: "rejected", reason: Error}
// ]
Promise.any()
const first = await Promise.any([
fetch('cdn1.com'),
fetch('cdn2.com'),
fetch('cdn3.com')
]);
5. Error Handling Patterns
Propagating Errors:
function apiCall() {
return fetchData()
.then(data => {
if(!data.valid) {
throw new Error('Invalid data');
}
return data;
});
}
Global Error Handling:
window.addEventListener('unhandledrejection', event => {
console.warn('Unhandled rejection:', event.reason);
});
6. Advanced Patterns
Promise Memoization:
const memoizedPromise = (fn) => {
let cache;
return () => cache || (cache = fn());
};
Retry Mechanism:
const retry = (fn, retries = 3) =>
fn().catch(err =>
retries > 1 ? retry(fn, retries - 1) : Promise.reject(err)
);
7. Promise Execution Flow
console.log('Start');
Promise.resolve()
.then(() => console.log('Microtask 1'))
.then(() => console.log('Microtask 2'));
setTimeout(() => console.log('Macrotask'), 0);
console.log('End');
// Output order:
// Start → End → Microtask 1 → Microtask 2 → Macrotask
Key Concepts:
- Microtask Queue: Promises execute before setTimeout/setInterval
- Event Loop: Promises are handled in the microtask queue
- Unhandled Rejections: Always include .catch() or try/catch
- Promise States: Immutable once settled
Best Practices:
- Always return promises from
.then()
handlers. - Prefer
async/await
for improved readability and cleaner code. - Use
Promise.all()
for executing multiple promises in parallel. - Handle errors at the appropriate level to avoid unhandled rejections.
- Avoid nesting promises; use promise chaining instead to prevent callback hell.
This understanding of promises will help you write cleaner, more maintainable asynchronous JavaScript code, making your applications more efficient and responsive to user interactions. By leveraging the power of promises, developers can create complex workflows and manage asynchronous operations effectively.