JavaScript Promises: A Complete Guide
Promises are a fundamental concept in JavaScript for handling asynchronous operations. They represent a value that may be available now, or in the future, or never. Understanding promises is crucial for modern JavaScript development.
What are Promises?
A Promise is an object representing the eventual completion or failure of an asynchronous operation. It allows you to write asynchronous code that looks synchronous, making it easier to read and maintain.
Basic Promise Creation
// Creating a simple promise
const myPromise = new Promise((resolve, reject) => {
// Asynchronous operation
setTimeout(() => {
const success = true;
if (success) {
resolve('Operation completed successfully!');
} else {
reject('Operation failed!');
...This example shows how to create a basic promise that resolves or rejects after a timeout.
Promise States
A promise can be in one of three states:
- Pending: Initial state, neither fulfilled nor rejected
- Fulfilled: Operation completed successfully
- Rejected: Operation failed
Promise States Example
const promise = new Promise((resolve, reject) => {
console.log('Promise created - State: Pending');
setTimeout(() => {
const random = Math.random();
if (random > 0.5) {
resolve('Success!'); // State: Fulfilled
} else {
reject('Error!'); // State: Rejected
}
...This demonstrates how a promise transitions from pending to either fulfilled or rejected.
Promise Methods: then(), catch(), finally()
Using then(), catch(), and finally()
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { id: 1, name: 'John Doe' };
resolve(data);
// reject(new Error('Network error'));
}, 1000);
});
};
...then() handles success, catch() handles errors, and finally() runs regardless of outcome.
Promise Chaining
Promises can be chained to perform sequential asynchronous operations. Each then() returns a new promise, allowing for clean sequential code.
Promise Chaining Example
const getUser = (id) => {
return new Promise((resolve) => {
setTimeout(() => resolve({ id, name: 'User ' + id }), 500);
});
};
const getPosts = (userId) => {
return new Promise((resolve) => {
setTimeout(() => resolve(['Post 1', 'Post 2', 'Post 3']), 500);
});
...This shows how to chain promises to perform sequential async operations.
Promise.all() - Running Promises in Parallel
Promise.all() takes an array of promises and returns a single promise that resolves when all promises in the array have resolved, or rejects if any promise rejects.
Promise.all() Example
const promise1 = new Promise(resolve => setTimeout(() => resolve('First'), 1000));
const promise2 = new Promise(resolve => setTimeout(() => resolve('Second'), 2000));
const promise3 = new Promise(resolve => setTimeout(() => resolve('Third'), 500));
Promise.all([promise1, promise2, promise3])
.then(results => {
console.log('All promises resolved:', results);
// Output: ['First', 'Second', 'Third']
})
.catch(error => {
...Promise.all() waits for all promises to resolve or rejects if any fail.
Promise.race() - First to Complete
Promise.race() returns a promise that resolves or rejects as soon as one of the promises in the array resolves or rejects.
Promise.race() Example
const slowPromise = new Promise(resolve =>
setTimeout(() => resolve('Slow'), 3000));
const fastPromise = new Promise(resolve =>
setTimeout(() => resolve('Fast'), 1000));
const failingPromise = new Promise((resolve, reject) =>
setTimeout(() => reject('Failed'), 500));
Promise.race([slowPromise, fastPromise])
...Promise.race() resolves with the first promise that completes.
Promise.allSettled() - Wait for All to Complete
Promise.allSettled() waits for all promises to settle (either resolve or reject) and returns an array of objects describing each promise's outcome.
Promise.allSettled() Example
const promises = [
Promise.resolve('Success 1'),
Promise.reject('Error 1'),
Promise.resolve('Success 2'),
new Promise(resolve => setTimeout(() => resolve('Success 3'), 1000))
];
Promise.allSettled(promises)
.then(results => {
results.forEach((result, index) => {
...Promise.allSettled() provides results for all promises, regardless of success or failure.
Error Handling Best Practices
Proper Error Handling
// ❌ Bad: No error handling
function riskyOperation() {
return fetch('/api/data');
}
// ✅ Good: Proper error handling
function safeOperation() {
return fetch('/api/data')
.then(response => {
if (!response.ok) {
...Always handle errors properly in promise chains and async functions.
Converting Callbacks to Promises
Promisifying Callback-based Functions
// Original callback-based function
function readFile(path, callback) {
// Simulate async file reading
setTimeout(() => {
if (Math.random() > 0.1) {
callback(null, 'File content');
} else {
callback(new Error('File not found'));
}
}, 1000);
...Convert callback-based functions to promise-based for better async handling.
Common Promise Patterns
Retry Pattern
function retryPromise(fn, maxRetries = 3, delay = 1000) {
return new Promise((resolve, reject) => {
function attempt(retriesLeft) {
fn()
.then(resolve)
.catch(error => {
if (retriesLeft > 0) {
setTimeout(() => attempt(retriesLeft - 1), delay);
} else {
reject(error);
...Implement retry logic for unreliable async operations.
Timeout Pattern
function withTimeout(promise, timeoutMs) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Operation timed out')), timeoutMs);
});
return Promise.race([promise, timeoutPromise]);
}
// Usage
const slowOperation = new Promise(resolve =>
...Add timeout functionality to promises to prevent hanging operations.
Real-World Example: API Calls
Complete API Example with Promises
class ApiService {
constructor(baseURL) {
this.baseURL = baseURL;
}
request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
return fetch(url, {
headers: {
...Build a complete API service using promises for all HTTP operations.
Key Takeaways
- Promises represent future values and help manage asynchronous operations
- Use
.then()for success handling and.catch()for errors Promise.all()runs promises in parallel,Promise.race()returns the first result- Always handle errors to prevent uncaught promise rejections
- Promise chaining enables sequential async operations
- Consider async/await for cleaner syntax (covered in the next section)
Ready to learn async/await? Check out the async/await guide for modern asynchronous JavaScript.