Callbacks are a fundamental concept in JavaScript and are used extensively in modern web development. A callback function is a function that is passed as an argument to another function and is called by that function when a certain event occurs. In this tutorial, we will discuss what callback functions are, how they work, and provide examples of when to use them.
1)- What Are Callback Functions?
Callback functions are simply functions that are passed as arguments to other functions. When the main function completes its task, it calls the callback function to perform an additional action. This additional action can be anything, from logging a message to making an API call.
The main advantage of using callbacks is that they allow us to write code that can handle asynchronous operations. An asynchronous operation is one that does not block the execution of the rest of the program. For example, making a network request to fetch data from a server can take some time, during which the rest of the program can continue to execute. When the data is ready, the callback function is called to handle the data.
2)- How many types of callbacks are there?
There are two types of callbacks: synchronous and asynchronous. Synchronous callbacks are executed immediately when the function that contains them is called. Asynchronous callbacks are executed at a later time, usually when an event occurs.
So, which one should you use? It all depends on the task at hand and your specific requirements. Ultimately, understanding the difference between synchronous and asynchronous callbacks can help you create more efficient and effective code.
3)- How do callback functions work?
To understand how callback functions work, let’s consider a simple example with synchronous code. Suppose we have a function that takes two numbers as arguments and returns their sum. We can add a callback function to this function to perform an additional operation on the sum.
function addNumbers(num1, num2, callback) {
let sum = num1 + num2;
callback(sum);
}
function printSum(sum) {
console.log(`The sum is ${sum}`);
}
addNumbers(5, 10, printSum);
In this example, we define a function called addNumbers that takes three arguments: num1, num2, and callback. Inside the function, we calculate the sum of num1 and num2 and store it in a variable called sum. We then call the callback function with sum as its argument.
We also define a function called printSum that takes a single argument sum and logs a message to the console. Finally, we call addNumbers with the arguments 5, 10, and printSum
.
When addNumbers is called, it calculates the sum of 5
and 10
, which is 15
. It then calls the printSum function with 15
as its argument. The printSum function logs the message “The sum is 15” to the console.
4)- How do callback functions work for asynchronous code?
Let’s take the following example:
function loginUser(email , password ){
setTimeout( ()=>{
return {userEmail : email};
} , 5000)
}
In this example we have a simple function loginUser that takes 2 parameters: email and password.
This function returns the email of the user after 5000 ms, which means 5 seconds, using the setTimeout function that allows us to run asynchronous code.
So now if we want to display the user email we have to invoke the function like this:
function loginUser(email,password){
setTimeout(()=>{
return {userEmail:email};
}, 5000)
}
var user = loginUser( "john@gmail.com" , 32654);
console.log (user);
//undefined
However, if we do it like that, we will get “undefined” as a result. Why? Because simply the email is returned after 5 seconds. But as the console.log() syntax is synchronous, javascript executes the synchronous code first and then the asynchronous, so javascript will execute the console.log() syntax first and it will not find the value of the user variable, because the value of the user variable is returned after 5 seconds.
So how to solve this problem?
Simply by adding a callback function as a third parameter of the loginUser function.
function loginUser(email,password, callback){
setTimeout(()=>{
callback ({userEmail:email});
}, 5000)
}
var user = loginUser("john@gmail.com", 32654, (emaildisplayed)=>{
console.log(emaildisplayed);
});
// {userEmail: 'john@gmail.com'}
After adding the callback as a third parameter, the return function has no use, it is replaced with the callback.
However to define what this callback should do, for example should it display the results or should it pass the results to another function or should it modify the results…it is done when the loginUser function is invoked inside the user variable.
Here we want the callback function to display the results, so we added the code (emaildisplayed) => console.log(emaildisplayed).
5)- Callback hell
Callback hell is a term used to describe the situation when code that uses multiple callbacks becomes unreadable and difficult to maintain. In such a case, the code becomes nested and hard to follow, with multiple layers of indentation that can lead to errors and confusion.
Callback hell often occurs when using asynchronous functions that depend on the result of another function to execute properly. As the number of these functions grows, the complexity of the code increases, leading to callback hell.
Let’s take the previous code where we get the user’s email address:
function loginUser(email,password, callback){
setTimeout(()=>{
callback ({userEmail:email});
}, 5000)
}
Now let’s say that besides the email, we want also to get the user’s contacts. And let’s say to have the user’s contacts we bsolutely have to have the email first. So the code will be:
function loginUser(email,password, callback){
setTimeout(()=>{
callback ({userEmail:email});
},5000)
}
function getUserContacts(email,callback){
setTimeout(()=>{
callback(["contact1","contact2","contact3"]);
},2000)
}
So now the getUserContacts function is linked to the email data that is in the first function loginUser. Which means that we will not have the contacts of the user unless we have their email first.
We have seen that we can have the email of the user via this code:
function loginUser(email,password, callback){
setTimeout(()=>{
callback ({userEmail:email});
},5000)
}
function getUserContacts(email,callback){
setTimeout(()=>{
callback(["contact1","contact2","contact3"]);
},2000)
}
var user = loginUser("john@gmail.com", 32654, (emaildisplayed)=>{
console.log(emaildisplayed);
});
So to pass the user email to the getUserContact function as a parameter and invoke the getUserContact function we’ll use the following code:
function loginUser(email,password, callback){
setTimeout(()=>{
callback ({userEmail:email});
},5000)
}
function getUserContacts(email,callback){
setTimeout(()=>{
callback(["contact1","contact2","contact3"]);
},2000)
}
var user = loginUser("john@gmail.com", 32654, (emaildisplayed)=>{
console.log(emaildisplayed);
getUserContacts(emaildisplayed.userEmail , (displayContacts)=>{
console.log(displayContacts);
})
});
//{userEmail: 'john@gmail.com'}
//['contact1', 'contact2', 'contact3']
Now let’s say that we want to have the phone number of the user’s contacts. So we will add another function called getPhoneNumber to do that. And this function will run only if we have the contacts from the previous function getUserContacts:
function loginUser(email,password, callback){
setTimeout(()=>{
callback ({userEmail:email});
},5000)
}
function getUserContacts(email,callback){
setTimeout(()=>{
callback(["contact1","contact2","contact3"]);
},2000)
}
function getPhoneNumber(contacts,callback){
setTimeout(()=>{
callback(["phoneNumber1","phoneNumber2","phoneNumber3"]);
}, 3000)
}
To display the phone numbers we should invoke the function getPhoneNumber as below:
function loginUser(email,password, callback){
setTimeout(()=>{
callback ({userEmail:email});
},5000)
}
function getUserContacts(email,callback){
setTimeout(()=>{
callback(["contact1","contact2","contact3"]);
},2000)
}
function getPhoneNumber(contacts,callback){
setTimeout(()=>{
callback(["phoneNumber1","phoneNumber2","phoneNumber3"]);
}, 3000)
}
var user = loginUser("john@gmail.com", 32654, (emaildisplayed)=>{
console.log(emaildisplayed);
getUserContacts(emaildisplayed.userEmail,(displayContacts)=>{
console.log(displayContacts);
getPhoneNumber(displayContacts[0],(displayPhones)=>{
console.log(displayPhones);
})
});
});
As you see, at this stage it becomes already messy and a little bit difficult to understand and manage the code. And this is what is referred to as callback hell.
And here where promises come into play, as they simplify this code and make it a lot easier.
6)- When to Use Callback Functions?
Callback functions are commonly used in asynchronous operations, such as making network requests, processing large files, or waiting for user input. They are also useful for handling events, such as mouse clicks or keyboard presses.
Here are some common scenarios where callback functions are used:
- Asynchronous operations: When making network requests or performing time-consuming operations, such as image processing, we can use callback functions to handle the results when they become available.
- Event handling: When handling user events, such as mouse clicks or keyboard presses, we can use callback functions to handle the event and update the user interface.
- Error handling: When handling errors that may occur during program execution, we can use callback functions to gracefully handle the error and provide feedback to the user.
7)- Conclusion
Callbacks are an essential part of JavaScript development, allowing you to write asynchronous code and pass functions as arguments to other functions. By understanding the fundamentals of callbacks and following best practices, you can use this powerful technique to create more efficient and effective JavaScript code. With this comprehensive guide, you now have the knowledge and tools you need to master callbacks in JavaScript.
Read More
Object constructor functions: How does the Javascript constructor work?
Send form data received in the backend to the database (MongoDB)
Send form data to the back-end (Node JS)
Get the data back from the database (MongoDB) and display it on the front-end
How to send data from back-end(Node JS) to MongoDB database?
Inline vs Inline-Block vs Block CSS properties