Callback Functions in JS: Why do they Exist?
What is a Callback Function?
A callback function is a function that is passed into another function as an argument, which is then invoked inside the outer function to complete a routine or action.
To break that down based on what we just went through:
"passed into another function as an argument": Instead of passing a string or a number to a function, we pass an actual, unexecuted block of code (the callback).
"invoked inside the outer function": The receiving function takes our code, does its own work first, and then literally calls your function.
"to complete a routine or action": It dictates what happens after the initial work is finished, whether that work was fetching data asynchronously or simply looping through an array synchronously.
Why do Callback Functions Exist?
The Problem: Synchronous Tasks Blocks the Thread
JavaScript is single-threaded language. It can only execute one line of code at a time. If a task takes five seconds to complete, a synchronous approach would freeze the entire application for those five seconds.
Ex: Imagine we have a function that fetches data from a database after which the code to load the UI is wriiten. If JavaScript executed this synchronously i.e. waiting for it to finish before moving on, and the database takes 3 seconds to respond, the browser locks up for 3 seconds and UI will load only after 3 seconds.
function fetchDatabaseSync(query) {
// Executing Query -- takes 3 seconds
user = { name: "Sam" };
return user;
}
console.log("1. Application started");
const user = fetchDatabaseSync("SELECT * FROM users");
// The application freezes here until the data arrives and Clicks, scrolls, and animations are dead.
console.log("2. User data loaded:", user.name);
console.log("3. UI Updated");
1. Application started
2. User data loaded: Sam
3. UI Updated
The Solution: Asynchronous Callback Function
Because functions in JavaScript are first-class citizens, meaning they can be treated and passed like any other variable, we can pass a function into our fetch mechanism. Inside our fetch function, after successfully completing the fetch, the function passed will be called inside out function.
function fetchDatabaseSync(query, logUserData) {
// Executing Query -- takes 3 seconds
user = { name: "Sam" };
logUserData(user);
return user;
}
console.log("1. Application started");
const user = fetchDatabaseSync("SELECT * FROM users", user => {
console.log("2. User data loaded:", user.name);
});
// The application does not freeze here and Clicks, scrolls, and animations are applied.
console.log("3. UI Updated");
1. Application started
3. UI Updated
2. User data loaded: Sam
Advantage of Callback: Code Reusability
While callbacks are famous for handling asynchronous network requests, they are also deeply embedded in synchronous JavaScript. They exist here to make code highly modular.
Ex: Instead of writing for loops every time you want to manipulate an array, JavaScript provides methods that accept a callback function, executing it immediately for every item.
const numbers = [1, 2, 3, 4, 5];
// The function inside .filter() is a synchronous callback.
// It executes immediately for each element.
const evens = numbers.filter(num => {
return num % 2 === 0;
});
console.log(evens); // Output: [2, 4]
Disadvantage of Callback: Callback Hell
Callbacks solved the single-thread problem, but the create a code structure problem when we have multiple asynchronous tasks that depend on the results of previous asynchronous tasks. The code looks like a pyramid also called Pyramid of Doom which are extremely difficult to debug.
// The Pyramid of Doom
getUser(userId, (user) => {
getPosts(user.id, (posts) => {
getComments(posts[0].id, (comments) => {
getAuthorDetails(comments[0].authorId, (author) => {
console.log("Finally got the author: ", author);
});
});
});
});
