Table of Contents
Open Table of Contents
What are callback functions?
A callback is a function passed into another function as an argument (like you pass variables) to be executed later.
For example:
function clickButton() {
console.log("Clicked Button!");
}
div.addEventListener("click", clickButton);
Here, the addEventListener method takes a callback clickButton function and then calls it when div is clicked.
Why do we need callbacks
JS is a single-threaded, event-driven programming language.
Single Threaded
JS is a single-threaded language and only has a single call stack, that means it can only execute a single thing at a time. Slow operations, like I/O, network requests, etc. will block the main thread and you will have to wait for these operations to complete before you can execute anything else.
data = request(url); // makes an http request to the passed url (5s)
console.log(data) // logs the data
Let’s assume that after 5 secs we got the response from our http request. Imagine having to wait 5 secs before you can do anything else on your favorite website. This doesn’t sound like a good User experience.
We can solve this problem by using callbacks. Consider this code snippet:
function request(url, cb) {
// Make http request to the url
cb(response); // execute the passed callback with the response
}
console.log("Start");
request('https://www.google.com', callback);
console.log("End");
This program kicks off the request method and starts executing next line in the main program. It doesn’t block the main thread as it doesn’t wait for the response. Once it gets the response back from the server and call stack is empty then the callback cb will be executed with this response.
Event Driven Language
JS is an event-driven language. This means it can listen for and respond to events, while continuing to execute further code and without blocking its thread.
Without callbacks your event listeners will look something like this:
button.addEventListener("click") {
console.log("Clicked Button");
}
Now all you can do on your website is Click a button and your website is waiting for you to do it. There’s no notion of asynchrony i.e. what would you like to do after someone clicks the button. You’re waiting for someone to do it now.
Again, you can resolve this via callbacks.
button.addEventListener("click", function() {
console.log("Clicked button");
})
Now you can do all the other things on a website, as you have attached a callback to the event click on your button. Now your button knows what to do after someone clicks it. It isn’t waiting for you to click it now.
Write more generic functions
Array methods like map, reduce and filter are called higher order functions because they take another function (callback) as an argument, which makes them more generic.
For example:
function incrementByOne(numbers) {
let results = [];
for (const number of numbers) {
results.push(number + 1);
}
return results;
}
let numbers = [3, 7, 10, 15];
console.log(incrementByOne(numbers)); // [4, 8, 11, 16]
The function incrementByOne takes a numbers array and returns a new array with all the numbers incremented by 1.
Let’s say we want to write a function which returns a new array with all the numbers getting doubled.
function double(numbers) {
let results = [];
for (const number of numbers) {
results.push(2 * number);
}
return results;
}
let numbers = [3, 7, 10, 15];
console.log(double(numbers)); // [6, 14, 20, 30]
The above two functions have so much in common, lets extract the common functionality from both the function and specify the specific behaviour you want via callback. Then the above implementation can be simplified as:
function map(numbers, cb) {
let results = [];
for (const number of numbers) {
results.push(cb(number));
}
return results;
}
function incrementByOne(number) {
return number + 1;
}
function double(number) {
return 2 * number;
}
let numbers = [3, 7, 10, 15];
console.log(map(numbers, incrementByOne)); // [4, 8, 11, 16]
console.log(map(numbers, double)); // [6, 14, 20, 30]
Synchronous Callbacks
The above implementation of map takes a callback and that callback is synchronous. This means that the function receiving the callback ( here its map) will have to wait for the callback (cb) execution to finish before it can resume its execution. This is called blocking. Hence, synchronous callbacks are blocking in nature. They will block the main thread and take it for themselves when they are invoked.
Also, synchronous callbacks are executed during the execution of the higher-order function that uses the callback. In the above implementation the callbacks incrementByOne and double are synchronous callbacks because they gets executed during the execution of the higher-order function map.
Asynchronous Callbacks
Asynchronous callbacks are non-blocking in nature. They are executed after the execution of the higher-order function that uses the callback.
We can simulate asynchronous callbacks via setTimeout function, which is an API provided by the browser.
console.log("Start");
// simulating asynchronous callback via setTimeout
setTimeout(() => {
console.log("Asynchronous callback");
}, 1000);
console.log("End");
Instead of waiting for the setTimeout to finish, the execution moves to the next line and we get the following output:
Start
End
Asynchronous callback
This is helpful because we don’t have to wait for slow operations like I/O, network requests to complete, we can move the execution to the next stage and once the async (or slow) operation is complete, the corresponding callback code will execute.