Promises are the foundation of modern async JavaScript. Think of a PromiseWhat is promise?An object that represents a value you don't have yet but will get in the future, letting your code keep running while it waits. like ordering food at a restaurant, you get a ticket (the Promise) immediately, and you can decide what to do when the food arrives (.then) or when the kitchen is out of ingredients (.catch). The ticket lets you go about your evening rather than standing at the counter waiting.
The three states of a promiseWhat is promise?An object that represents a value you don't have yet but will get in the future, letting your code keep running while it waits.
Every Promise starts as pending and transitions exactly once to either fulfilled or rejected. Once settled, a Promise never changes state.
| State | Meaning | Handler |
|---|---|---|
pending | Operation still running | , |
fulfilled | Completed successfully | .then(value => ...) |
rejected | Failed with an error | .catch(error => ...) |
const myPromise = new Promise((resolve, reject) => {
// Simulate an async operation
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve('Data loaded!'); // -> fulfilled
} else {
reject(new Error('Load failed')); // -> rejected
}
}, 1000);
});resolve or reject calls happen later. This trips up many beginners, new Promise(...) does not pause your code.Consuming a promiseWhat is promise?An object that represents a value you don't have yet but will get in the future, letting your code keep running while it waits. with then and catch
Once you have a Promise, you attach handlers to it using .then() and .catch(). Each call returns a new Promise, which is what makes chaining possible.
fetchUserData(userId)
.then(user => {
console.log('User:', user.name);
return fetchUserPosts(user.id); // Return another Promise
})
.then(posts => {
console.log('Posts:', posts.length);
})
.catch(error => {
// Catches any error from the entire chain above
console.error('Something failed:', error.message);
})
.finally(() => {
// Runs whether success or failure
console.log('Request finished');
});Think of .catch() like a safety net stretched under the entire chain. Any rejection above it falls straight down to the net.
Async/awaitWhat is async/await?A syntax that lets you write asynchronous code (like fetching data) in a readable, step-by-step style instead of chaining callbacks.: promises in disguise
async/await is syntax sugar over Promises. Under the hood, an async function always returns a PromiseWhat is promise?An object that represents a value you don't have yet but will get in the future, letting your code keep running while it waits. and await pauses execution until a Promise settles. The code looks synchronous but behaves asynchronously.
async function loadUserData(userId) {
try {
const user = await fetchUserData(userId);
console.log('User:', user.name);
const posts = await fetchUserPosts(user.id);
console.log('Posts:', posts.length);
return { user, posts };
} catch (error) {
console.error('Something failed:', error.message);
return null;
} finally {
console.log('Request finished');
}
}This is exactly equivalent to the chained version above, choose whichever reads more clearly for your situation.
Running promises in parallel
One common mistake is await-ing promises one by one when they do not depend on each other. That makes them sequential when they could run at the same time.
// Slow: three round-trips, sequential
const users = await fetchUsers();
const posts = await fetchPosts();
const comments = await fetchComments();
// Fast: all three start at the same time
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments()
]);Promise.all is all-or-nothing. If any Promise rejects, the whole thing rejects. If you want partial results, use Promise.allSettled instead.Choosing the right parallel combinator
| Method | Resolves when | Rejects when | Use case |
|---|---|---|---|
Promise.all | All fulfill | Any rejects | Need all results |
Promise.allSettled | All settle | Never | Want all results, including failures |
Promise.race | First settles | First rejects | Timeout pattern |
Promise.any | First fulfills | All reject | First successful result |
// Promise.allSettled - get every result regardless of success
const results = await Promise.allSettled([
fetchUser(1),
fetchUser(2),
fetchUser(999) // May fail
]);
const successful = results
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
// Promise.race - useful for timeouts
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 5000)
);
const data = await Promise.race([fetchData(), timeout]);Error handling patterns
There is no single correct way to handle PromiseWhat is promise?An object that represents a value you don't have yet but will get in the future, letting your code keep running while it waits. errors. Here are the three patterns you will encounter most often.
// Pattern 1: try/catch - easiest to read
async function getData() {
try {
return await fetchData();
} catch (err) {
console.error('Failed:', err.message);
return null; // Return a safe fallback
}
}
// Pattern 2: catch and rethrow with context
async function getUser(id) {
try {
return await fetchUser(id);
} catch (err) {
throw new Error(`Could not load user ${id}: ${err.message}`);
}
}
// Pattern 3: chained fallbacks
fetchData()
.catch(() => fetchBackupData()) // Try a backup source
.catch(() => defaultData) // Final fallback value
.then(data => render(data));