JavaScript Promises
What is a Promise?
A Promise is a JavaScript object that represents the eventual result of an asynchronous operation. Instead of passing callbacks into a function, a promise lets you attach handlers to the future success or failure of that operation. This makes async code much easier to read and reason about.
A Promise is always in one of three states:
Once a promise is fulfilled or rejected it is settled and its state never changes again.
const promise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve('Operation succeeded!');
} else {
reject(new Error('Operation failed!'));
}
});
promise
.then(result => console.log(result)) // Operation succeeded!
.catch(error => console.error(error)); // only runs on rejection
Chaining Promises
Each .then() returns a new promise, which allows you to chain multiple async steps in a readable sequence. The value returned from one .then() is passed as the argument to the next.
fetch('https://api.example.com/user/1')
.then(response => response.json()) // parse JSON
.then(user => {
console.log(user.name);
return fetch(`/api/posts?userId=${user.id}`);
})
.then(response => response.json())
.then(posts => console.log(posts))
.catch(error => console.error('Error:', error))
.finally(() => console.log('Done')); // always runs
Promise.all() — Run in Parallel
Promise.all() takes an array of promises and returns a single promise that resolves when all of them resolve, or rejects as soon as any one of them rejects. Use it when tasks are independent and can run simultaneously.
const p1 = fetch('/api/users').then(r => r.json());
const p2 = fetch('/api/posts').then(r => r.json());
const p3 = fetch('/api/comments').then(r => r.json());
Promise.all([p1, p2, p3])
.then(([users, posts, comments]) => {
console.log(users, posts, comments);
})
.catch(err => console.error('One failed:', err));
Promise.allSettled(), race(), any()
JavaScript provides several other static methods for handling multiple promises:
const slow = new Promise(res => setTimeout(() => res('slow'), 2000));
const fast = new Promise(res => setTimeout(() => res('fast'), 500));
const fail = Promise.reject(new Error('failed'));
// allSettled — never rejects
Promise.allSettled([slow, fast, fail]).then(results => {
results.forEach(r => console.log(r.status, r.value ?? r.reason));
});
// race — first to settle wins
Promise.race([slow, fast]).then(v => console.log(v)); // 'fast'
// any — first to FULFILL wins
Promise.any([fail, fast]).then(v => console.log(v)); // 'fast'
Error Handling in Promises
Always attach a .catch() at the end of a promise chain to handle any rejection that bubbles up. The .finally() handler runs regardless of success or failure — useful for cleanup like hiding a loading spinner.
function loadUser(id) {
return fetch(`/api/users/${id}`)
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
});
}
loadUser(42)
.then(user => console.log('Loaded:', user.name))
.catch(err => console.error('Failed:', err.message))
.finally(() => console.log('Request complete'));
- A Promise has three states: pending, fulfilled, and rejected. Once settled it never changes.
- .then() handles fulfillment, .catch() handles rejection, .finally() always runs.
- Promise chains pass return values from one .then() to the next — always return a value or another promise.
- Promise.all() runs promises in parallel and fails fast if any one rejects.
- Promise.allSettled() waits for all promises regardless of outcome — use it when you need all results.
- Always handle rejections with .catch() to avoid unhandled promise rejection warnings.