Loading...
Loading...
The original async pattern. Pass a function to be called when work is done.
Browsers and servers spend most of their time waiting: for network responses, timers, and user input. Async JavaScript lets your program stay responsive while work happens in the background using callbacks, Promises, and async/await.
Learn Async Callbacks in JavaScript: covering Async Callbacks, Error-First Pattern, Callback Hell, Fixing Callback Hell with interactive visual examples and step-by-step explanations.
Synchronous code runs one line at a time. But real applications wait for network requests, file reads, and timers. Async JavaScript lets your program keep running while that work happens in the background: without freezing the UI.
This module covers the full async story: callbacks, Promises, async/await, and the Fetch API. Visual timelines show when tasks enter the queue, when they resolve, and why async/await is syntactic sugar over Promises, not magic.
Understanding async callbacks is essential for every JavaScript developer. It shows up in frontend UI code, backend APIs, and framework internals, and getting it wrong leads to bugs that are hard to trace. This chapter builds intuition with visuals so the behavior sticks.
A function you pass that gets called when an async operation completes.
// Synchronous callback (runs immediately): [1, 2, 3].forEach(n => console.log(n)); // Asynchronous callback (runs LATER): setTimeout(() => { console.log("I run after 1 second"); }, 1000);
The key difference: async callbacks don't run immediately. They're stored and called when the async operation finishes.
Node.js convention: first argument is always the error.
// Error-first callback pattern: function readFile(path, callback) { setTimeout(() => { if (path === "") { callback(new Error("Path is empty"), null); } else { callback(null, "file contents here"); } }, 100); } // Usage: always check error first: readFile("data.txt", (error, data) => { if (error) { console.error("Failed:", error.message); return; // stop here! } console.log("Got:", data); });
Convention:
callback(error, result)
• If error is null → success, use result
• If error exists → failure, handle error
Nested callbacks become unreadable. The 'Pyramid of Doom'.
// The problem: sequential async operations getUser(userId, (err, user) => { if (err) return handleError(err); getOrders(user.id, (err, orders) => { if (err) return handleError(err); getOrderDetails(orders[0].id, (err, details) => { if (err) return handleError(err); getShipping(details.trackingId, (err, shipping) => { if (err) return handleError(err); // 😱 4 levels deep! And it gets worse... displayShipping(shipping); }); }); }); });
Problems with callback hell:
Named functions and modularization help, but Promises are the real solution.
// Fix 1: Named functions (flatten the pyramid) function handleUser(err, user) { if (err) return handleError(err); getOrders(user.id, handleOrders); } function handleOrders(err, orders) { if (err) return handleError(err); getOrderDetails(orders[0].id, handleDetails); } function handleDetails(err, details) { if (err) return handleError(err); getShipping(details.trackingId, displayShipping); } // Start the chain: getUser(userId, handleUser);
// Fix 2: Promises (the modern solution) getUser(userId) .then(user => getOrders(user.id)) .then(orders => getOrderDetails(orders[0].id)) .then(details => getShipping(details.trackingId)) .then(shipping => displayShipping(shipping)) .catch(handleError); // one error handler! // Fix 3: Async/Await (cleanest) async function showShipping(userId) { const user = await getUser(userId); const orders = await getOrders(user.id); const details = await getOrderDetails(orders[0].id); const shipping = await getShipping(details.trackingId); displayShipping(shipping); }
Where you still see callbacks today.
// Event listeners (always callbacks): button.addEventListener("click", () => { console.log("Clicked!"); }); // Array methods: [1, 2, 3].map(n => n * 2); // setTimeout / setInterval: setTimeout(() => console.log("later"), 1000); // Node.js fs (legacy API): const fs = require("fs"); fs.readFile("file.txt", "utf8", (err, data) => { if (err) throw err; console.log(data); }); // Geolocation API: navigator.geolocation.getCurrentPosition( (pos) => console.log(pos.coords), (err) => console.error(err) );
Callbacks aren't dead: event listeners, array methods, and many APIs still use them. But for sequential async operations, Promises and async/await are far better.
Common questions about async callbacks.