Asynchronous JavaScript & Promises
JavaScript is single-threaded, meaning it can only do one thing at a time. However, operations like loading images, querying databases, or requesting external APIs take time. If these operations blocked the main thread, the entire browser would freeze.
Asynchronous JavaScript prevents this blocking.
1. The Callback Era & Callback Hell
Before ES6, asynchronous programming was managed using callbacks:
getData(function(data) {
getProfile(data.id, function(profile) {
getAvatar(profile.username, function(avatar) {
console.log(avatar);
});
});
});This deeply nested pattern is known as Callback Hell or the Pyramid of Doom. It is hard to read, trace, and debug.
2. What is a Promise?
Introduced in ES6, a Promise represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
A Promise is in one of three states:
- Pending: Initial state; the operation is still running.
- Fulfilled: The operation completed successfully.
- Rejected: The operation failed with an error.
// Creating a Promise
const fetchUserData = new Promise((resolve, reject) => {
let success = true;
setTimeout(() => {
if (success) {
resolve({ id: 1, name: "Alice" }); // Operation succeeded
} else {
reject("Failed to fetch user data."); // Operation failed
}
}, 1000);
});3. Consuming Promises: then, catch, and finally
You consume a Promise using the .then(), .catch(), and .finally() methods:
.then(callback): Runs if the Promise is fulfilled (receives the resolved value)..catch(callback): Runs if the Promise is rejected (receives the error)..finally(callback): Runs regardless of the outcome (great for closing loaders or database connections).
fetchUserData
.then((user) => {
console.log("User data retrieved:", user);
})
.catch((error) => {
console.error("Error occurred:", error);
})
.finally(() => {
console.log("Operation complete.");
});4. Concurrent Promises: Promise.all
If you have multiple independent asynchronous operations, running them sequentially wastes time. You can run them in parallel using Promise.all:
const fetchUsers = new Promise(res => setTimeout(() => res(["Alice", "Bob"]), 1000));
const fetchPosts = new Promise(res => setTimeout(() => res(["Post 1", "Post 2"]), 1500));
// Starts both in parallel:
Promise.all([fetchUsers, fetchPosts])
.then(([users, posts]) => {
console.log("Users:", users);
console.log("Posts:", posts);
})
.catch((err) => {
console.error("One of the promises failed:", err);
});Note: Promise.all is "all-or-nothing". If even a single promise rejects, the entire aggregate rejects immediately.
In the next guide, we will learn how to write cleaner asynchronous code using async and await.