JavaScript Promises: Handling Async Operations
Master JavaScript Promises. Understand async operations, promise chaining, error handling, and Promise.all.
JavaScript Promises
The Problem: Things That Take Time
Imagine you order food online. You don't stand at the door waiting - you do other things, and when the food arrives, you handle it.
JavaScript works the same way. Some things take time:
┌─────────────────────┐
│ You: Fetch data │
│ from API │
│ (takes 2 seconds) │
└──────────┬──────────┘
│
┌───────────▼───────────┐
│ JavaScript doesn't │
│ wait! It continues │
│ with other code │
└───────────────────────┘
│
┌───────────▼───────────┐
│ 2 seconds later... │
│ API responds │
│ Now what? │
└───────────────────────┘
The problem: How do you handle the response when it arrives? Promises solve this.
What is a Promise?
A Promise is like a receipt. You get it immediately, but the actual result comes later.
Promise Lifecycle:
┌─────────────────────┐
│ 1. Pending │ ← Just created, waiting
│ (Waiting...) │
└──────────┬──────────┘
│
┌───────┴───────┐
│ │
┌───▼───┐ ┌───▼───┐
│ 2. │ │ 3. │
│Fulfilled│ │Rejected│
│(Success)│ │(Error) │
└────────┘ └───────┘
Three states:
- Pending: Waiting for result
- Fulfilled: Got the result (success)
- Rejected: Something went wrong (error)
Step-by-Step: Creating a Promise
Step 1: Create a new Promise
const myPromise = new Promise((resolve, reject) => {
});
Step 2: Add your async operation
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
// This runs after 1 second
}, 1000);
});
Step 3: Call resolve on success
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Data loaded!');
}, 1000);
});
Step 4: Call reject on error
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve('Success!');
} else {
reject('Failed!');
}
}, 1000);
});
Step-by-Step: Using a Promise
Step 1: Call .then() for success
myPromise.then(result => {
console.log(result); // "Success!"
});
Step 2: Add .catch() for errors
myPromise
.then(result => {
console.log(result);
})
.catch(error => {
console.error(error);
});
What happens:
1. Promise starts (Pending)
│
2. After 1 second...
│
3. If success → .then() runs
If error → .catch() runs
Promise Chaining: One After Another
Sometimes you need to do things in order:
fetch('https://api.example.com/user')
.then(response => response.json())
.then(user => {
console.log('Got user:', user);
return fetch(`https://api.example.com/posts/${user.id}`);
})
.then(response => response.json())
.then(posts => {
console.log('Got posts:', posts);
})
.catch(error => {
console.error('Something failed:', error);
});
Flow diagram:
Step 1: Fetch user
│
▼
Step 2: Convert to JSON
│
▼
Step 3: Get user ID, fetch posts
│
▼
Step 4: Convert posts to JSON
│
▼
Step 5: Use the posts
Each .then() waits for the previous one to finish.
Promise.all: Do Multiple Things at Once
When you need to wait for multiple things:
Promise.all([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments')
])
.then(([users, posts, comments]) => {
console.log('All done!');
console.log('Users:', users);
console.log('Posts:', posts);
console.log('Comments:', comments);
})
.catch(error => {
console.error('One of them failed:', error);
});
How it works:
┌─────────────┐
│ Start all │
│ 3 requests │
└──────┬──────┘
│
┌───┴───┐
│ │ │
┌──▼──┐ ┌──▼──┐ ┌──▼──┐
│User │ │Post │ │Comm │
│API │ │API │ │API │
└──┬──┘ └──┬──┘ └──┬──┘
│ │ │
└───┬───┴───┬───┘
│ │
┌───────▼───────▼───────┐
│ Wait for ALL │
│ to finish │
└───────────────────────┘
│
┌───────▼───────┐
│ Then run code│
└───────────────┘
Important: If any promise fails, the whole thing fails.
Real Example: Loading User Data
function loadUserData(userId) {
return fetch(`/api/users/${userId}`)
.then(response => {
if (!response.ok) {
throw new Error('User not found');
}
return response.json();
})
.then(user => {
console.log('User loaded:', user.name);
return user;
})
.catch(error => {
console.error('Failed to load user:', error);
throw error;
});
}
// Use it
loadUserData(123)
.then(user => {
console.log('Got user:', user);
});
Common Patterns
Pattern 1: Handle errors properly
promise
.then(result => {
// Success
})
.catch(error => {
// Always handle errors!
console.error(error);
});
Pattern 2: Return promises in .then()
fetch('/api/data')
.then(response => response.json())
.then(data => {
return fetch(`/api/more/${data.id}`); // Return next promise
})
.then(response => response.json());
Quick Tips
- Always handle errors - Use .catch() or your app might break silently
- Return promises in .then() - This lets you chain them
- Use Promise.all for parallel operations - Faster than doing them one by one
- Promises are everywhere - fetch(), setTimeout(), file operations all return promises
Promises are essential for modern JavaScript. They let you handle async operations cleanly. Once you understand promises, async/await will make much more sense.