You order a pizza and give them your phone number. You don't stand at the door waiting. You go play. When the pizza is ready, they call you back. A callback is that phone number.
A function you hand over
A callback is a function you pass to another function. That other function decides when to call it.
function orderPizza(callMe) {
// ...making pizza...
callMe("Pizza's here!");
}
orderPizza(msg => console.log(msg)); // prints: Pizza's here!
orderPizza gets your function as callMe. When the pizza is done, it calls callMe and passes along a message. You hand over the phone number, and they call you back.
The name callMe is just a parameter name. Any name works, and you'll often see callback, cb or done.
You don't have to write the callback inline. You can pass a function that already has a name. Leave off the parentheses, because you are handing over the function, not calling it yet.
function sayThanks(msg) {
console.log("Thanks! " + msg); // prints: Thanks! Pizza's here!
}
function orderPizza(callMe) {
callMe("Pizza's here!");
}
orderPizza(sayThanks);
Inside orderPizza, callMe and sayThanks are the same function under two names.
You've used them already
The function you give to map or filter is a callback. map calls it once for each item.
const scores = [3, 8, 5];
const doubled = scores.map(n => n * 2);
console.log(doubled); // prints: [ 6, 16, 10 ]
setTimeout takes one too. It waits a number of milliseconds, then calls your function. Two thousand milliseconds is two seconds.
setTimeout(() => console.log("Ding"), 2000); // prints: Ding
console.log("Waiting..."); // prints: Waiting...
"Waiting..." prints first, and "Ding" two seconds later. JavaScript doesn't stand at the door. It keeps going and gets called back when the timer is done.
Some jobs take time, like a timer or a request to the internet. Callbacks let JavaScript carry on with other work instead of freezing until the job is finished.
The twist: callback hell
When one slow job needs the result of another, you put a callback inside a callback inside a callback.
function getUser(id, done) { setTimeout(() => done({ id, name: "Mia" }), 10); }
function getOrders(user, done) { setTimeout(() => done(["pizza"]), 10); }
getUser(1, user => {
getOrders(user, orders => {
console.log(user.name, orders); // prints: Mia [ 'pizza' ]
});
});
With two steps it's fine. With four or five, the code drifts further and further to the right. This is called callback hell. It's hard to read and hard to fix, and you have to handle errors again at every level. Promises fix this, and they are the next lesson.
Another problem is trust. When you hand your callback to someone else's code, they decide when and how often to call it. A bug on their side could call it twice, or never.
When to use callbacks
- Array methods like
map,filter,forEachandsort. - Timers, with
setTimeoutandsetInterval. - Events, such as "run this when the button is clicked".
- Older Node.js functions, which often call back with
(err, data), error first.
For a chain of slow steps, reach for promises or async and await instead.
Callbacks are not going away, though. Even promises and await are built on the same idea of handing over a function to be called later.
Rule of thumb
A callback is a function you hand over, for someone else to call.
Calling the function instead of handing it over. setTimeout(sayHi(), 1000) runs sayHi right away and passes its result. Pass the function itself with setTimeout(sayHi, 1000), or wrap it as () => sayHi().
Check yourself
setTimeout(() => console.log("Ding"), 2000), which part is the callback?The arrow function is handed to setTimeout, which calls it back after 2000 milliseconds.