JavaScript Promises
A Promise is an object representing the eventual completion (or failure) of an asynchronous operation and its resulting value. Before Promises, JavaScript relied heavily on callbacks, which often led to difficult-to-read "Callback Hell" (the Pyramid of Doom). Promises provide a cleaner, more structured way to handle async code.
1. The Three States of a Promise
A Promise is always in one of these three mutually exclusive states:
- Pending: The initial state. The async operation has not completed yet.
- Fulfilled (Resolved): The operation completed successfully. The promise now holds a resulting value.
- Rejected: The operation failed. The promise now holds a reason for the failure (usually an Error object).
Once a promise is fulfilled or rejected, it is considered settled. A promise's state can only change once; you cannot reject a resolved promise, nor can you resolve a rejected one.
2. Creating a Promise
You construct a promise using the new Promise() constructor, which takes an executor function. The executor is given two functions as arguments: resolve and reject.
const myPromise = new Promise((resolve, reject) => {
// Simulate an async operation (e.g., fetching data)
setTimeout(() => {
const success = true;
if (success) {
resolve("Data loaded successfully!"); // Transitions to Fulfilled
} else {
reject(new Error("Network failure")); // Transitions to Rejected
}
}, 1000);
});
3. Consuming Promises (.then, .catch, .finally)
To interact with the result of a promise, we attach consumer methods.
.then(onFulfilled, onRejected): Called when the promise is fulfilled..catch(onRejected): A shorthand for.then(null, onRejected). Called when the promise is rejected..finally(onFinally): Called when the promise settles, regardless of success or failure. Great for cleanup tasks (like hiding a loading spinner).
myPromise
.then((data) => {
console.log(data); // "Data loaded successfully!"
})
.catch((error) => {
console.error(error.message);
})
.finally(() => {
console.log("Operation completed (success or fail).");
});
4. Promise Chaining
The true power of Promises comes from chaining. Because .then() always returns a new Promise, you can chain multiple asynchronous operations sequentially.
// Example using fetch (which returns a Promise)
/*
fetch('/api/user/1')
.then(response => response.json())
.then(user => fetch(`/api/posts/${user.id}`))
.then(response => response.json())
.then(posts => console.log("User's posts:", posts))
.catch(error => console.error("Chain failed:", error));
*/
Note: A single .catch() at the end of the chain will catch errors generated by any of the preceding promises.
5. Promise Concurrency Methods
Sometimes you need to coordinate multiple asynchronous operations at once. The Promise class offers static methods for this.
Promise.all(iterable)
Takes an array of promises. Returns a single promise that resolves when all promises have resolved. If even one promise rejects, the entire Promise.all immediately rejects.
/*
Promise.all([fetchUsers(), fetchSettings()])
.then(([users, settings]) => {
console.log("Both APIs finished loading", users, settings);
})
.catch(err => console.error("One of them failed", err));
*/
Promise.allSettled(iterable)
Introduced in ES2020. Waits for all promises to settle (either resolve or reject). It never rejects itself; instead, it returns an array of objects describing the outcome of each promise.
/*
Promise.allSettled([promiseA, promiseB])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log("Success:", result.value);
} else {
console.error("Failed:", result.reason);
}
});
});
*/
Promise.race(iterable)
Returns a promise that fulfills or rejects as soon as the first promise in the iterable settles. It's a "race" to completion. Useful for implementing API timeouts.
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000));
// Whichever finishes first wins
/*
Promise.race([fetchLargeFile(), timeout])
.then(data => console.log("File loaded"))
.catch(err => console.error(err));
*/
Promise.any(iterable)
Introduced in ES2021. Returns a promise that resolves as soon as any one of the promises resolves. It only rejects if all promises reject (returning an AggregateError). Useful for querying redundant APIs and taking the first successful response.