You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
JavaScript is single-threaded — it can execute only one piece of code at a time. Yet web applications constantly perform tasks that take time: fetching data from APIs, reading files, waiting for user input, and running timers. Asynchronous programming lets JavaScript handle these tasks without freezing the page.
If JavaScript ran everything synchronously, a network request taking 3 seconds would freeze the entire page for 3 seconds — no scrolling, no clicking, no animations. Asynchronous code solves this by starting a task, moving on, and handling the result when it is ready.
JavaScript's concurrency model relies on the event loop:
.then, .catch) go here (higher priority than macrotasks).┌───────────────────────┐
│ Call Stack │
└──────────┬────────────┘
│ (empty?)
▼
┌───────────────────────┐
│ Microtask Queue │ ← Promises (.then, .catch, .finally)
└──────────┬────────────┘
│ (empty?)
▼
┌───────────────────────┐
│ Macrotask Queue │ ← setTimeout, setInterval, I/O
└───────────────────────┘
The original pattern for async code. A callback is a function you pass to another function to run later:
console.log("Start");
setTimeout(() => {
console.log("Inside timeout (after 2 seconds)");
}, 2000);
console.log("End");
// Output:
// "Start"
// "End"
// "Inside timeout (after 2 seconds)"
Nested callbacks become hard to read and maintain:
getUser(userId, (user) => {
getOrders(user.id, (orders) => {
getOrderDetails(orders[0].id, (details) => {
console.log(details);
// More nesting...
});
});
});
This "pyramid of doom" is why promises were introduced.
A Promise represents a value that may be available now, in the future, or never. It has three states:
| State | Meaning |
|---|---|
| Pending | The operation has not completed yet |
| Fulfilled | The operation completed successfully (has a value) |
| Rejected | The operation failed (has a reason/error) |
const myPromise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("It worked!");
} else {
reject(new Error("Something went wrong"));
}
});
myPromise
.then((value) => {
console.log("Success:", value);
})
.catch((error) => {
console.error("Error:", error.message);
})
.finally(() => {
console.log("Done (runs regardless)");
});
Each .then() returns a new promise, enabling clean chains:
fetch("https://api.example.com/user/1")
.then((response) => response.json())
.then((user) => {
console.log("User:", user.name);
return fetch("https://api.example.com/orders/" + user.id);
})
.then((response) => response.json())
.then((orders) => {
console.log("Orders:", orders.length);
})
.catch((error) => {
console.error("Something failed:", error);
});
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.