JS Async Callbacks
In this page:
function functionName(callback) {
// async work
callback(result);
}
functionName(function(result) {
// use result
});
What a Callback Is
A callback is nothing more than a regular function passed as an argument to another function -- the receiving function calls it back at some later point, often once an asynchronous operation (like a timer or a network request) finishes.
Example: What a Callback Is
// Define the function `greet` taking `name`, `callback`
// Define the function `greet` taking `name`, `callback`
function greet(name, callback) {
// Call `callback(`Hello, ${name}`)`
// Call `callback(`Hello, ${name}`)`
callback(`Hello, ${name}`);
}
// Call `greet("Sam", (message) => console.log(message))`
// Call `greet("Sam", (message) => console.log(message))`
greet("Sam", (message) => console.log(message));
Callbacks with Asynchronous Operations
The classic use of a callback is pairing it with something that takes time -- setTimeout(callback, delay) calls the callback after the delay elapses, and older AJAX APIs used callbacks to run code once a network response arrived, since there was no other way to react to something happening "later".
Example: Callbacks with Asynchronous Operations
setTimeout(() => console.log("Runs later"), 500);
// Older AJAX used callbacks similarly, run once a response arrives
The Error-First Callback Convention
A widely-adopted convention, especially in Node.js-style code, is the "error-first" callback: callback(error, result), where the first argument is either an Error object (if something went wrong) or null (if it succeeded) -- checking that first argument becomes the standard way to handle failure in callback-based code.
Example: The Error-First Callback Convention
// Define the function `loadData` taking `callback`
// Define the function `loadData` taking `callback`
function loadData(callback) {
// Call `setTimeout(() => callback(null, { id: 1 }), 100)`
// Call `setTimeout(() => callback(null, { id: 1 }), 100)`
setTimeout(() => callback(null, { id: 1 }), 100);
}
loadData((error, result) => {
if (error) console.log("Failed:", error);
else console.log("Success:", result);
});
The Problem: Callback Hell
When several asynchronous steps must happen in sequence, each depending on the previous one's result, callback-based code nests deeper and deeper -- a pattern commonly called 'callback hell' or 'the pyramid of doom', which becomes genuinely hard to read, modify, and debug as more steps are added.
Example: The Problem: Callback Hell
function step1(cb) { setTimeout(() => cb(1), 10); }
function step2(a, cb) { setTimeout(() => cb(a + 1), 10); }
function step3(b, cb) { setTimeout(() => cb(b + 1), 10); }
step1((a) => {
step2(a, (b) => {
step3(b, (c) => console.log("Deeply nested result:", c)); // callback hell
});
});
Callbacks Still in Use Today
Despite Promises and async/await being preferred for most sequential async logic, plain callbacks remain common and appropriate for certain patterns -- event listeners (addEventListener), array iteration methods (forEach, map), and any 'run this every time X happens' scenario that is not really about a single eventual result.
Example: Callbacks Still in Use Today
document.addEventListener("click", () => console.log("Clicked")); // still a normal, appropriate callback use
[1, 2, 3].forEach(n => console.log(n)); // another normal callback use
- Nesting callback inside callback inside callback for a sequence of dependent async steps, producing deeply indented, hard-to-follow code often called 'callback hell'.
- Following the error-first callback convention inconsistently, forgetting to check the error argument before using the result argument.
- Calling a callback function immediately (with parentheses) instead of passing a reference to it, executing it right away instead of scheduling it for later.
- A callback is simply a function passed as an argument to another function, to be invoked later once some operation completes.
- The error-first convention (callback(error, result)) is a common pattern where the first argument is either an error object or null.
- Deeply nested callbacks for sequential async steps produce hard-to-read "callback hell", which Promises and async/await were designed to solve.
The callback pattern itself is a basic JavaScript language feature, supported since the language's creation, and works in every environment.
Chapter Quiz — Complete all 26 topics to unlock
0/26 topics done
Complete these topics first:
- JS Dates
- JS Math
- JS Conditionals
- JS Switch
- JS Loop For
- JS Loop While
- JS Iterables
- JS Sets
- JS Maps
- JS typeof
- JS Type Conversion
- JS Destructuring
- JS Arrow Functions
- JS Classes
- JS Modules
- JS Promises
- JS Async/Await
- JS DOM
- JS DOM Methods
- JS Events Advanced
- JS DOM Navigation
- JS DOM Collections
- JS Async Callbacks
- JS Async Parallel
- JS Date Set
- JS Set Logic