Asynchronous JavaScript
Old async code used nested callbacks. Many callbacks inside callbacks create "callback hell" — hard to read and debug.
The callback problem
Old async code used nested callbacks. Many callbacks inside callbacks create "callback hell" — hard to read and debug.
Real-life example: Calling a friend who calls another friend who calls a third person just to ask one question — too many steps in a chain.
getUser(1, (user) => {
getCourses(user.id, (courses) => {
getLesson(courses[0].id, (lesson) => {
console.log(lesson.title); // deeply nested
});
});
});
// Promises and async/await fix this stylePromises
A Promise represents work that will finish later — success (resolve) or failure (reject). Chain .then and .catch instead of nesting.
Real-life example: A food delivery promise — "We will bring pizza in 30 minutes or give your money back." You wait without blocking the door.
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
wait(1000)
.then(() => fetch("https://jsonplaceholder.typicode.com/posts/1"))
.then((res) => res.json())
.then((post) => console.log(post.title))
.catch((err) => console.error(err));async and await
async marks a function that returns a Promise. await pauses inside that function until a Promise finishes — code looks straight, like sync code.
Real-life example: await is like waiting at the counter for your coffee order number to be called — you do other things on your phone, but your turn comes in order.
async function loadPost() {
const res = await fetch("https://jsonplaceholder.typicode.com/posts/1");
const post = await res.json();
return post.title;
}
loadPost().then(console.log);fetch example
fetch is the modern way to get data from URLs in the browser. It returns a Promise.
Real-life example: fetch is like sending a waiter to the kitchen (server) to bring your dish (JSON data) back to your table (browser).
async function getCourses() {
const response = await fetch("/api/courses");
if (!response.ok) {
throw new Error("Network error: " + response.status);
}
return response.json();
}Error handling
Always wrap async code in try/catch. Network fails, servers return errors, and JSON can be invalid.
Real-life example: Ordering online — if payment fails, you see an error message instead of a blank screen. Good apps always plan for failure.
async function safeLoad() {
try {
const res = await fetch("/api/data");
if (!res.ok) throw new Error("HTTP " + res.status);
return await res.json();
} catch (error) {
console.error("Could not load:", error.message);
return { courses: [] }; // fallback
}
}Event loop — how async works
JavaScript runs one task at a time on the call stack. Slow jobs (timers, fetch) go to Web APIs. When done, callbacks join a queue and return to the stack.
Real-life example: A single cashier (call stack) serves one customer. While pizza bakes (Web API), others wait in line (queue) — the shop never freezes.