Callbacks are a very important topic to understand in javascript. In this article, We will be seeing what Callbacks are and how to use them.
What are Callbacks
First, we will see a regular javascript function, and from there we will see how callbacks are used.
Regular Javascript function
So first let us look at a normal Function in javascript.
function multiply(a, b) {
var result = a * b;
console.log("multiply Function Result:",result);
}
multiply(2, 4);
Here we have a simple function which multiplies 2 numbers. We are then calling the function with inputs 2 and 4.
Callbacks Example 1
Now imagine if you had to run another operation immediately after multiply computes the result. This is where we use a callback. The Below code shows this.
function multiply(a, b, callback) {
var result = a * b;
console.log("multiply Function Result:",result);
callback(result);
}
function checkOddEven(result){
var isEven = result % 2 == 0
if(isEven){
console.log('checkOddEven Function:',result,' is Even');
}
else
{
console.log('checkOddEven Function:',result,' is Odd');
}
}
multiply(7, 9, checkOddEven);
Here in the multiply function, we accept a callback as well as the input.
when we call the multiply function we pass callback as checkOddEven. So basically a callback is nothing but a function. checkOddEven is a function which checks whether a number is odd or even.
In the multiply function, at the end, we have callback(result). This is where we ask the callback function to execute.
So in the above code, the sequence is as follows
- First we call the multiply function and pass checkOddEven as the callback
- multiply function executes and computes the multiplication result
- once the result is calculated, multiply function asks the callback to execute.
- In this case, the callback is checkOddEven function. So checkOddEven function will execute.
The result of the above code is shown below
multiply Function Result: 63
checkOddEven Function: 63 is Odd
We can pass any function to the callback.
Callbacks Example 2
Let's take the following script
function multiply(a, b, callback) {
var result = a * b;
console.log("multiply Function Result:",result);
callback(result);
}
function checkPosNeg(result){
var isPositive = result >= 0;
if(isPositive){
console.log('checkPosNeg Function:',result,' is Positive');
}
else
{
console.log('checkPosNeg Function:',result,' is Negative');
}
}
multiply(-7, 9, checkPosNeg);
Here we have a function called checkPosNeg which checks whether the number is positive or negative.
We are passing the callback as checkPosNeg in this Example.
The output of the above program is given below
multiply Function Result: -63
checkPosNeg Function: -63 is Negative
From this example, we see that any function can be passed to the callback.
Anonymous Callback Function
Another way of passing a callback is by using anonymous functions. The code for this is shown below.
function multiply(a, b, callback) {
var result = a * b;
console.log("multiply Function Result:", result);
callback(result);
}
multiply(-7, 9, function(result) {
if (result > 0) {
console.log('checkPosNeg Function:', result, ' is Positive');
} else {
console.log('checkPosNeg Function:', result, ' is Negative');
}
});
In this case, we see that the callback function is created at the same time we are calling the multiply function. This function basically checks whether the number is positive or negative but the function does not have any name.
Error Handling in Callbacks
The below code snippet shows how to do error handling in callbacks.
function divide(a, b, callback) {
if (b != 0) {
var result = a / b;
console.log('divide Function Result', result);
callback(null, result);
} else
callback(new Error('Divide by 0 Error:' + a + '/' + b))
}
function checkPosNeg(error, result) {
if (error) {
console.log('checkPosNeg Function cannot run');
console.log(error);
} else {
var isPositive = result >= 0;
if (isPositive) {
console.log('checkPosNeg Function:', result, ' is Positive');
} else {
console.log('checkPosNeg Function:', result, ' is Negative');
}
}
}
divide(4, 0, checkPosNeg);
In this case, we have a function called divide which has a callback checkPosNeg.
Now when b is 0, then the division is not possible. If the division is not possible, then we cannot send any result to the callback.
So in this case we define the callback function as checkPosNeg(error,result).
Whenever division is possible we call callback(null,result) indicating there is no Error and everything is good.
If the division is not possible then we call callback(new Error('Error message')) which tells that there is an error.
Now in checkPosNeg function we need to check for error as well. In case error is not null then we need to take necessary action in the code. For example, here we are just printing the error message.
Why do we need Callbacks
The obvious question which you might have is why do we even need callbacks.
Letβs take the following code snippet
console.log('Task1');
makeServerCall(url,function(error,result){
console.log('Task2');
});
console.log('Task3');
In the Above code first Task1 is printed.
Next makeServerCall function makes a network call.
Now Will Task3 be printed before or after Task2?
Generally, whenever we make a network call, the Code continues to the next statement and does not wait for the result in sync.
So the moment the network call is made, the code continues to the next statement and prints Task3.
Once the network call completes and the response comes back then Task2 is printed.
So here makeServerCall takes a callback as its input. So once the server call completes, it executes the callback.
In this case, the callback enables us to run some operation once the network call is complete without blocking the code ( i.e the future statements are not blocked until the network call is complete).
Callback of Callbacks
Callbacks can be Chained Together.
Take the following code snippet.
function1(input1, function(error, result1) {
function2(result1, function(error, result2) {
function3(result2, function(error, result3) {
console.log('Callback Chain')
})
})
})
- Here first we wait for function1 to complete the network call and execute the first callback.
- The first callback, in turn, calls function2. Once function2 completes its network call, it executes the second callback.
- The Second callback calls function3. Once function3 completes its network call, it executes the third callback.
- The Third callback just prints a message.
More callbacks can be chained together as well.
Something doesn't seem right here
Well, as you might have already noticed in the above script, it becomes a little unreadable as the number of callbacks increase.
The above example shows just one-liner functions. If the functions were slightly bigger and the number of chained callbacks was more, then the code will be highly unreadable. Also, this means it is very very hard to debug the code.
A sample snippet is here to illustrate this
function1(input1, function(error, result1) {
if (error) {
console.log('Error')
} else {
function2(result1, function(error, result2) {
if (error) {
console.log('Error')
} else {
function3(result2, function(error, result3) {
if (error) {
console.log('Error')
} else {
function4(result3, function(error, result4) {
if (error) {
console.log('Error')
}
})
}
})
}
})
}
})
This problem is known as the Pyramid of doom.
One way to get around this is to use Promises which I will be covering in a future article
Congrats π
You now know what are callbacks and how to use them.
Happy Coding π
This post was originally published in adityasridhar.com
Top comments (2)
The intent of promises is not to get rid of "pyramid of doom" as promise themselves can have it, It has to do with the trust issue and Inversion of control in callbacks, as a callback cannot be trusted to be called either once or multiple times or not at all when being passed to an external API. Either with the error or without error or both. The error first callback is a mere convention in nodejs.
Promises solved this problem as they can only have a finite number of states in output (fulfilled in case success or rejected in case of error) and can only be done once in either.
It also inverts the "Inversion of control" by making an event-based system in which we don't have to depend on external API to call the passed callback function, hence reducing testing overhead and more control over callback. The promise API is easy to reason about than callbacks.
Thanks Deepak for explaining more on promise in detail.