You know how you ask someone to call you back in a phone conversation? That’s what callback functions are. They are functions to be called later; after something has happened.
In order to grasp this in practice, one needs to understand two things
- that there is a difference between a function name
fn
and a function callfn()
, - that functions can be passed into functions as arguments, the same way that numbers, strings and arrays are passed into functions as arguments
The difference between a function name and a function call
Let’s consider the code snippet below
function fn() {
console.log("I am a sweet simple function");
}
We have defined a function fn
. That means that we have created a function and stored it under the identifier fn
. If we log fn
to the console with console.log(fn)
, we’ll see the code that we created. It makes sense to say that we have gotten fuel and we have given it to fn
.
Fuel has the ability to burn, but it will not burn unless it is ignited. The function that we have given to fn
is like fuel. It can be executed but it would not be executed unless we ignite it. How do we ignite it? By simply adding parentheses to the name of the function in our code.
fn(); // [Console] "I am a sweet simple function"
fn
is the name of the function. fn()
is its execution.
Functions as arguments
Let us consider the code snippet below
function acceptArgumentAndLogIt(arg) {
console.log(arg);
}
acceptArgumentAndLogIt("an argument"); // [Console] "an argument"
acceptArgumentAndLogIt
simply accepts an argument under the identifier arg
and logs it.
In the last line of the snippet, “an argument” is passed to acceptArgumentAndLogIt
and arg
then represents “an argument”. What happens if instead of accepting an argument and logging it, we accept a function as an argument, and run it. Let’s see!
function acceptFunctionAndRunIt(fn) {
fn();
}
function logSomething() {
console.log("something");
}
acceptFunctionAndRunIt(logSomething); // [Console] "something"
We defined two functions. acceptFunctionAndRunIt
accepts a function and runs it (as in our fuel analogy, it ignites it by appending parentheses to it). The other logSomething
holds the ability to log “something” to the console.
In the last line of the snippet, we call acceptFunctionAndRunIt
and pass in logSomething
to it. logSomething
will to be represented as fn
within it and because of this, fn
will be a reference to logSomething
. When acceptFunctionAndRunIt
runs, it sees fn
and asks what does fn
refer to? The JavaScript runtime rightly tells it that it refers to the the function assigned to the label logSomething
. That function is retrieved and executed, and then “something” appears on the console.
So what is a callback function?
Any function A, that is passed into another function B to be executed within function B is a callback function. The reason why it is called by that name is because function A is usually passed into function B so that function B can call it after running some operations. It will be easier to grasp if instead of saying “callback”, one says “call later”, as in the phone conversation analogy.
Imagine that you have a phone conversation with a friend, and you need information from them, but they don’t have it at hand, so you say ‘call me back when you have it’. Your friend goes on to get the information and then calls you or ignites you afterwards. Let’s make a code snippet out of this.
function useInfo(info) {
console.log(info);
}
function getInfo(giveInfoToFriend) {
// getInfoFromBagOfSecrets() returns ["secret one","secret two"],
// takes about 2 mins, hypothetically
const info = getInfoFromBagOfSecrets();
giveInfoToFriend(info);
}
getInfo(useInfo);
When the JavaScript runtime runs, it stores the two functions defined under their respective identifiers. In the last line of the snippet, we call getInfo
and pass in useInfo
as an argument. When getInfo
runs, it accepts the reference to the function assigned to useInfo
as giveInfoToFriend
, thus within getInfo
, useInfo
is the same as giveInfoToFriend
.
Information is gotten from the bag of secrets and is passed to giveInfoToFriend
.
In order to execute useInfo
, we add the parentheses and pass info
to it. The execution causes what info
represents to be logged to the console. The function passed in as giveInfoToFriend
is a callback function because it was passed in to be called at a later time, that is, when the information has been retrieved.
Real life examples
Callback functions exist in many places and you’ve seen them, you probably didn’t see them that way.
window.addEventListener("eventname", callbackFn)
Remember, a callback function is one that is passed into another function to be called at a later time. If we have window.addEventListener("click", callbackFn)
in our code, we do not know when the user will click on the window, so we say whenever the user clicks, pass in the necessary arguments to callbackFn
and run it. callbackFn
will be executed at a later time, specifically when the user clicks on the window.
fs.read(fd, [options,] callbackFn)
In Node.js, we have the ability to read files present on the computer. The fs.read
function is an asynchronous one, in otherwords, it may or may not be completely run before the code that is written after it. Since we do not know when it will be completely run, we pass in callbackFn
when we execute it and say look, we don’t know when you’ll be done running, but whenever you’re done, whatever result that you get, pass it into callbackFn
and then run callbackFn
.
Image credits