Callbacks
Functions are no different from numbers, strings, or arrays. They are just a special data type (higher-order object), a value that can be stored in a variable or passed as an argument to another function.
function greet(name) {
return `Welcome ${name}.`;
}
// Calling the greet function and displaying the result in the console
console.log(greet("Mango")); // Welcome, Mango.
// Displaying the greet function in the console without calling it
console.log(greet); // ƒ greet() { return `Welcome ${name}.`; }
In the first log, the greet
function is called with parentheses, and its
result is displayed in the console. The second log passes a reference to the
function, and not the result of its call (there are no parentheses), which is
why its body is displayed in the console. This means that a function can be
assigned to a variable or passed as an argument to another function.
Callback function, or callback is a function that is passed as an argument to another function, and the latter calls such function.
Higher-order function is a function that takes other functions as parameters or returns a function as a result.
// Callback
function greet(name) {
console.log(`Welcome ${name}.`);
}
// Higher-order function
function registerGuest(name, callback) {
console.log(`Registering guest ${name}.`);
callback(name);
}
registerGuest("Mango", greet);
A reference to the greet
function has been passed as an argument, so it will
be assigned to the callback
parameter and called inside the registerGuest
function using parentheses. The callback parameter name can be arbitrary, the
main thing to remember is that its value will be a function.
Inline callbacks
If the callback function is small and is only needed to be passed as an argument, it can be declared immediately when calling the function to which the callback is being passed. Such a function will be available only as a parameter value and nowhere else in the code.
function registerGuest(name, callback) {
console.log(`Registering guest ${name}.`);
callback(name);
}
// Passing the greet inline function as a callback
registerGuest("Mango", function greet(name) {
console.log(`Welcome ${name}.`);
});
// Passing the notify inline function as a callback
registerGuest("Poly", function notify(name) {
console.log(`Dear ${name}, your room will be ready in 30 minutes.`);
});
Several callbacks
Any function can assume an arbitrary number of callbacks. For example, let's say you are developing a logic for handling phone calls. The program should activate the answering machine if the subscriber is not available, or connect the call otherwise. To simulate the availability of the subscriber, use a random number generator so that different results can be obtained between different function calls.
function processCall(recipient) {
// Simulating the subscriber’s availability with a random number
const isRecipientAvailable = Math.random() > 0.5;
if (!isRecipientAvailable) {
console.log(
`The subscriber ${recipient} is not available, please leave a message.`
);
// Answering machine activation logic
} else {
console.log(`Connecting you to ${recipient}, please wait...`);
// Call handling logic
}
}
processCall("Mango");
The problem with this approach is that the processCall
function does too much
and binds the subscriber availability check to two predefined actions. What if
in the future, instead of an answering machine, a hologram will be used?
You could change the function to return a value and then, based on its execution result, make checks and execute the necessary code. But checks are not related to external code and will clutter it.
Let's refactor the function so that it receives two callbacks, onAvailable
and
onNotAvailable
, and calls them conditionally.
function processCall(recipient, onAvailable, onNotAvailable) {
// Simulating the subscriber’s availability with a random number
const isRecipientAvailable = Math.random() > 0.5;
if (!isRecipientAvailable) {
onNotAvailable(recipient);
return;
}
onAvailable(recipient);
}
function takeCall(name) {
console.log(`Connecting you to ${name}, please wait...`);
// Call handling logic
}
function activateAnsweringMachine(name) {
console.log(
`The subscriber ${name} is not available, please leave a message.`
);
// Answering machine activation logic
}
function leaveHoloMessage(name) {
console.log(`The subscriber ${name} is not available, recording a hologram.`);
// Hologram record logic
}
processCall("Mango", takeCall, activateAnsweringMachine);
processCall("Poly", takeCall, leaveHoloMessage);
Callbacks are used to process user actions on the page, process requests to the server, execute unknown functions, etc. This is what they are used for: these are functions intended for deferred execution.
Repetition abstraction
Abstraction is hiding implementation details. It helps you think about tasks at a higher (abstract) level. Functions are a good way to create abstractions.
For example, a script performs an action a certain number of times. To do this,
you can use the for
loop.
for (let i = 0; i < 10; i += 1) {
console.log(i);
}
Can you abstract "do something N times" as a function? Yes, let's write a
function that calls console.log()
N times.
function repeatLog(n) {
for (let i = 0; i < n; i += 1) {
console.log(i);
}
}
repeatLog(5);
What if you want to do something other than logging numbers? Since “do something” can be thought of as a function, and functions are just values, you can pass an action as an argument.
function printValue(value) {
console.log(value);
}
function prettyPrint(value) {
console.log("Logging value: ", value);
}
function repeat(n, action) {
for (let i = 0; i < n; i += 1) {
action(i);
}
}
// Passing printValue as a callback
repeat(3, printValue);
// 0
// 1
// 2
// Passing prettyPrint as a callback
repeat(3, prettyPrint);
// Logging value: 0
// Logging value: 1
// Logging value: 2