Understand asynchronous JavaScript with Promises — creating, chaining, and handling success and failure states.
JavaScript is single-threaded — it can only execute one piece of code at a time. If a long-running operation (like a network request or a timer) ran synchronously, the entire page would freeze until it finished.
Asynchronous programming solves this by letting code continue running while waiting for slow operations to complete. The result is delivered later, when it is ready.
Callbacks were the original solution:
setTimeout(function() {
console.log('Done after 1 second');
}, 1000);
console.log('This runs immediately');But callbacks quickly lead to deeply nested, hard-to-read code known as "callback hell":
getUser(id, function(user) {
getOrders(user, function(orders) {
getDetails(orders[0], function(details) {
// 3 levels deep and growing...
});
});
});Promises were introduced in ES6 to solve this problem by providing a cleaner, chainable API for asynchronous operations.
A Promise is an object that represents a value that may not be available yet but will be at some point in the future (or will fail).
A Promise has three possible states:
Once a Promise is fulfilled or rejected, it is settled and its state cannot change again.
Creating a Promise:
const myPromise = new Promise((resolve, reject) => {
// Do some async work...
const success = true;
if (success) {
resolve('It worked!'); // Fulfill the promise
} else {
reject('Something failed'); // Reject the promise
}
});Consuming a Promise:
myPromise
.then(value => console.log(value)) // 'It worked!'
.catch(error => console.log(error)) // handles rejection
.finally(() => console.log('Done')); // runs either way.then(onFulfilled) — called when the Promise is fulfilled..catch(onRejected) — called when the Promise is rejected..finally(onSettled) — called when the Promise is settled (fulfilled or rejected), useful for cleanup.One of the most powerful features of Promises is chaining. Each .then() call returns a new Promise, allowing you to chain multiple asynchronous steps in sequence.
fetch('/api/user')
.then(response => response.json()) // Parse JSON
.then(user => fetch('/api/orders/' + user.id)) // Fetch orders
.then(response => response.json()) // Parse JSON
.then(orders => console.log(orders)) // Use the data
.catch(error => console.error(error)); // Handle ANY errorKey rules of chaining:
.then() wraps it in a resolved Promise automatically:Promise.resolve(1)
.then(n => n + 1) // Returns 2, wrapped in Promise.resolve(2)
.then(n => n * 3) // Returns 6
.then(n => console.log(n)); // 6Returning a Promise from .then() is awaited before the next .then() runs.
Errors propagate down the chain to the nearest .catch():
Promise.resolve('start')
.then(() => { throw new Error('Oops'); })
.then(() => console.log('Skipped')) // Skipped!
.catch(err => console.log(err.message)); // 'Oops'This flat, readable chain replaces the nested callback pattern entirely.
JavaScript provides several static methods on the Promise constructor for working with multiple Promises at once.
Promise.all(iterable) — waits for all Promises to fulfill. If any rejects, the whole result rejects:
Promise.all([fetchUser(), fetchOrders(), fetchSettings()])
.then(([user, orders, settings]) => { /* all done */ })
.catch(err => { /* one failed */ });Promise.race(iterable) — resolves or rejects with the first Promise to settle:
Promise.race([fetchData(), timeout(5000)])
.then(data => console.log(data))
.catch(() => console.log('Timed out'));Promise.allSettled(iterable) — waits for all Promises to settle (no short-circuit on rejection). Each result has { status, value } or { status, reason }:
Promise.allSettled([p1, p2, p3])
.then(results => {
results.forEach(r => console.log(r.status)); // 'fulfilled' or 'rejected'
});Promise.any(iterable) — resolves with the first Promise to fulfill. Only rejects if all reject (with an AggregateError):
Promise.any([fetchFromCDN1(), fetchFromCDN2()])
.then(data => console.log('Got data from fastest CDN'));<div id="output"></div>
<script>
// Simulate an async operation with setTimeout + Promise
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id: id, name: 'Alice' });
} else {
reject('Invalid user ID');
}
}, 500);
});
}
const output = document.getElementById('output');
output.innerHTML = '<p>Loading...</p>';
fetchUser(1)
.then(user => {
output.innerHTML = `<p>User: ${user.name} (ID: ${user.id})</p>`;
return user.name.toUpperCase();
})
.then(upperName => {
output.innerHTML += `<p>Uppercase: ${upperName}</p>`;
})
.catch(error => {
output.innerHTML = `<p>Error: ${error}</p>`;
})
.finally(() => {
output.innerHTML += '<p>Request complete.</p>';
});
</script>What are the three states of a Promise?