Passing a callback to another function
To pass a callback, name the function as an ordinary argument when you call another function, and inside the receiving function call it by its parameter name. This works because functions in JavaScript are values (first-class citizens): they can be assigned to variables, stored in arrays and passed as arguments.
Theory
TL;DR
- A callback is passed as an ordinary argument:
doSomething(afterDone). - Pass a reference to the function, with no parentheses. Parentheses mean a call and pass its result, often
undefined. - Inside the receiving function the callback is called by its parameter name:
callback(). - You can pass a named function, an anonymous function, or an arrow function written inline at the call site.
- The receiving function does not care what the callback does: it receives that behaviour from outside.
Quick example
function doSomething(callback) {
console.log('Doing something...');
callback(); // call the callback we were given
}
function afterDone() {
console.log('Done!');
}
doSomething(afterDone);Output:
Doing something...
Done!Three ways to pass a callback
The notation differs, the idea is the same: a function ends up in the callback parameter.
// 1. A named function, we pass a reference to it
function afterDone() {
console.log('Done!');
}
doSomething(afterDone);
// 2. An anonymous function right in the argument
doSomething(function () {
console.log('Done!');
});
// 3. An arrow function, the shortest form
doSomething(() => {
console.log('Done!');
});| Form | When it is convenient |
|---|---|
| Named function | The logic is reused in several places and is easy to test on its own |
Anonymous function | You need the callback to have its own this or arguments |
| Arrow function | A short one-off handler, this is taken from the surrounding scope |
Passing arguments into the callback
The receiving function decides which arguments the callback is called with. The caller only describes what to do with those values.
function doSomething(callback) {
const result = 42;
callback(result); // pass data into the callback
}
doSomething(value => {
console.log('Got', value); // Got 42
});If you need to fix your own arguments up front, wrap the call in an arrow function or use bind.
function greet(name) {
console.log('Hello, ' + name);
}
doSomething(() => greet('Maria')); // wrapper keeps the argument
doSomething(greet.bind(null, 'Oleh')); // the same via bindCalling instead of passing: the main trap
afterDone is a function, afterDone() is already its result. Parentheses in the argument run the function immediately, so the receiving function gets a value instead of behaviour.
doSomething(afterDone); // correct: pass the function itself
doSomething(afterDone()); // wrong: runs now and passes undefinedThe same principle holds for any API that takes a callback: setTimeout(fn, 100), addEventListener('click', fn), array.map(fn).
Guarding against a missing callback
If the parameter is optional, check it before calling or give it a default value, otherwise you get TypeError: callback is not a function.
function doSomething(callback = () => {}) {
console.log('Doing something...');
callback();
}
doSomething(); // works even without an argumentCommon mistakes
- Passing
afterDone()instead ofafterDone: the function runs immediately and the parameter receivesundefined. - Writing parentheses in
setTimeout(sayHi(), 100)and then wondering why the timer does nothing. - Calling the callback without checking that one was actually passed, and getting a
TypeError. - Passing an object method directly,
obj.handler, and losingthis. An arrow wrapper orbindfixes it. - Expecting a value from an asynchronous callback to be available right after the function call: it only appears once the callback actually runs.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.