In the previous article, we went around a couple of issues that are faced by a newbie JavaScript programmer. We also came across what is called a callback function while fetching the information using an AJAX request. Let us try to understand a callback function in depth and how to go about using the same in our JavaScript code.
There is no standard definition of what a callback function is. But, callback functions are very common in asynchronous programming. We can get to know the JavaScript callback function defined as follows.
A callback function is a function which is:
The outer function in the above reference, to which the callback function is passed as an argument, is a function which is actually undertaking the async task or delegating the same to another nested async function. Let us see in the examples below for both callback functions and the main function.
Let us go through 2 JavaScript callback functions examples to understand the above definition. A Network Call based example
function getUserName(callback){
var name;
$.get('https://randomuser.me/api/', function(data) {
name = data.results[0].name.first
+ " " + data.results[0].name.last;
callback(name);
});
}
var username ;
function callback(res){
username = res;
document.write("Name: " + username);
}
getUserName(callback);
In the above example, we are fetching the user details using an AJAX call and then using the callback function to show the name of the user. The callback function is responsible for using the fetched data and then updating the same in the DOM. The data is being fetched in the main function getUserName
var count = 0;
//callback 1
function updateCount(){
$("#count").html(count);
}
//callback 2
function incCount() {
count++;
updateCount();
}
//callback 3
function resetCount(){
count=0;
updateCount();
}
$(document).ready(updateCount);
$("#inc").click(incCount);
$("#reset").click(resetCount);
In the example, we are using 3 different callbacks, namely updateCount, incCount and resetCount for different events that are occurring on the page. As soon as the page loads, the count is updated on the page. Then, every time you click on the “Increment Me” button, it increments the counter in the code and also updates the same on the page. Similarly, the “Reset Me” button resets the counter. The main functions here are the jQuery event handlers functions like ready and click, which operate on the DOM elements.
Practice: Can you try and implement a decrement button with the above example?
Like the above examples, a callback function would be necessary when you are dealing with any of the items on the following list:
JavaScript code executes on a single thread and that too asynchronously. It means that the code execution stack doesn’t wait on any I/O operation, which happens outside the JavaScript thread, for example, a file read operation. Callback functions are a means to drive the execution of the code once the I/O operation is completed.
This helps in a good way as it does not block the single threaded JavaScript code execution while waiting over any I/O operation and can execute the other functions in the stack. This is important in case of browsers, where the entire user experience is managed by the JavaScript rich applications since the inception of JavaScript.
The way callbacks are managed internally is using an event loop. We shall talk about the JavaScript call stack and event loop in the articles to come.
Imagine the following sequence of actions that are to be performed in JavaScript.
I have tried to implement the above using a demo API called JSON PlaceHolder for implementing the code. You can checkout the link to see the documentation of the same.
Let’s try and look at the code of the implementation of the above sequence of actions.
function updateUserInfo(data){
var out = "";
for(var key in data){
if ( key!== 'id' && typeof data[key] !== "object") {
out += "<div>"+
key + ": " + data[key] +
"</div><br>";
}
}
$("#user").html(out);
}
function updateUserPosts(data){
var out = "";
//pulling out just the first post
for(var key in data[0]){
if ( key!== 'id' && typeof data[0][key] !== "object") {
out += "<div>"+
key + ": " + data[0][key] +
"</div><br>";
}
}
$("#post").html(out);
}
function updatePostComments(data){
var out = "";
//pulling out just the first post
for(var i=0; i<data.length; i++) {
for(var key in data[i]){
if ( key!== 'id' && typeof data[i][key] !== "object") {
out += "<div>"+
key + ": " + data[i][key] +
"</div><br>";
}
}
out+="<hr/>"
}
$("#comments").html(out);
}
function fetchUserInfo(userId, callback, err){
$.ajax("https://jsonplaceholder.typicode.com/users/"+userId, {
success: function(data) {
setTimeout(function(){
callback(data)
},1000);
},
error: err
});
}
function fetchUserPosts(userId, callback, err) {
$.ajax("https://jsonplaceholder.typicode.com/posts/?userId="+userId, {
success: function(data) {
setTimeout(function(){
callback(data)
},1000);
},
error: err
});
}
function fetchPostComments(postId, callback, err) {
$.ajax("https://jsonplaceholder.typicode.com/posts/"+postId+"/comments", {
success: function(data) {
setTimeout(function(){
callback(data)
},1000);
},
error: err
});
}
function errCallback(data){
console.log("Error:" + data.status);
}
// Callback Hell
fetchUserInfo("2", function(user){
// console.log(data);
updateUserInfo(user);
fetchUserPosts(user.id, function(posts){
updateUserPosts(posts);
fetchPostComments(posts[0].id, function(comments){
updatePostComments(comments)
}, errCallback);
}, errCallback);
}, errCallback);
I have implemented each of the action as a separate function. For example, the function fetchUserInfo is only going to fetch the user information with a given id and use a callback to pass on the information to the parent function. The function updateUserInfo is only going to update a section of the page with the pass on information. Notice that fetch function are using a callback to pass on the information to the parent function and update functions do not. Along with that, we have a very generic error handler, which is the same for all the actions and prints just the request status. The last piece of code is the actual execution of the sequence.
Now, let’s look at the issues with the above implementation.
For each action, where we are fetching the data, there is a success callback and then there is an error callback. Both scenarios are important to handle for each action, hence more functions.
In the example above, we are using a generic error handler. But real life actions are not so simple. Imagine the sequence of a checkout pipeline on an e-commerce website. What if you are about the pay for an item that is not available? You would want to go back a step and inform the user about the same, won’t you?. This would lead to a separate handler at each step for both success and failure scenario.
The example we are dealing with is a very simple one. Say, we are to also fetch the images that are associated with a user id along with user posts and also update the same before the posts. We will have to rewrite a sequence of code and also all the supporting function to support success and error scenarios. How about we add the albums and then their comments too? :)
We already know the user id, hence we should be able to fetch the user information and posts parallelly. But in our implementation, we first have to fetch the user information and then the user posts. There should be a cleaner way to deal with the same.
Promises is a representation of a sync operation in the form of an object. It provides a more stateful approach to any async operation and tries to structure the implementation and its attachment with the callbacks. We shall talk about promises in more details in the next article.
Let me hear your thoughts on this article. Any suggestions and word of improvements are also welcome.
Leave a Reply
Your email address will not be published. Required fields are marked *