JavaScript: Callback Functions

With this article, I begin a three-part series of articles based on Asynchronous JavaScript; we’ll be discussing Callback functions, Promises and Async/Await.

I assume you understand JavaScript functions and objects as we’ll be using these a lot in this article. If you’re new to JavaScript and haven’t started with functions and objects yet, I advise you to check out the function/objects sections in the W3Schools JavaScript tutorial.

What is Asynchronous JavaScript?

In JavaScript, code is always run synchronously, in a sense that some codes block the execution flow. Let’s take a look at the scenario below:

1.     Make request to server for file `clients.json` [size: 5MB], 
convert string content to actual JSON object, 
log contents to console.
2.     Initialize variable `name = “Kojo Rawson”`, log variable name to console

Explanation:

  • In the above scenario, we’ll call each line a process. The first process is to send a request to a server for a file called clients.json which is 5 megabytes huge; when a response (the received file) is sent to us, we then grab the contents of the JSON file (which is a string type) and parse it into an actual object literal; and then we log the properties to the console.
  • In the second process, we simply initialize a variable name with a string value and finally log this string to the console by calling the variable.

Synchronous and Asynchronous Code Execution

  • In the first process, the request made to the server to grab a 5 megabytes file means that the client (browser) was supposed to wait for the server to respond to the request, then execute the following sub-processes before heading on to execute the second process; this could have taken milliseconds, seconds or even minutes depending on the internet speed of the user. This execution style is done synchronously. With synchronous code execution, JavaScript code would run on one single thread; meaning our code does one thing at a time. In real world, this could affect the performance of our applications since important processes, which the user may need while waiting for the response, would not run because execution flow is paused, waiting for a response, before continuing.

  • JavaScript allows us to run code in an asynchronous manner. Instead of blocking execution flow while waiting for a response, JavaScript goes ahead to execute any following codes, and when the response is finally received, it is dealt with in the manner as programmed. So, even though the request is made to the server (first process) before the second process, we see the results in a counterclockwise order: we see the results from the second process before that of the first. In this article, instead of making an actual request to a server, we’ll take advantage of the global setTimeout() higher-order function which runs a function after a specified number of milliseconds. The time it takes before invoking the function will be assumed as the time taken to receive a response from an actual server.

Now, let’s get to the subject matter of this article.

JavaScript Callback Functions

We know that functions (in JavaScript) are objects. Hence, they can be passed to other functions as arguments, and also can be returned by other functions. A function that takes other functions as arguments, or returns another function is called a higher-order function. Sounds familiar? Okay, what about callbacks? Callbacks are functions that are passed as parameters to another function. Let’s digest this…

Consider an example of callbacks with the higher-order .forEach() array method:

let words = [ 'mass', 'weight', 'height' ];
words.forEach( function (word, index) {
    console.log( index + 1 + ': ' + word );
});

In the above example, we iterate over the words array with the array higher-order method forEach()which has an anonymous function (with the word and index arguments). The forEach() method is said to be a higher-order function because it took a function as an argument; which function? The anonymous one of course! So, this means our callback function is the anonymous function that is passed as an argument to the forEach()method. Let’s take another example with addEventListener():

let btn = document.querySelector('.btn');
let yield = function (e) {
    alert('Button is clicked!');
};
btn.addEventListener('click', yield, false);

Here, the addEventListener() method accepts the function yield as an argument making it a higher-order function, and the yield function the callback. Simply put, a callback function is a function that is passed as an argument to a function (e.g. mainFunction) and later called inside the mainFunction (outer) function.

Okay, so what? Why do we even need callbacks?

Why use Callback functions?

JavaScript runs code asynchronously; it is event-driven. This means JavaScript, whiles running other processes (like server requests, setTimeout, etc.), keeps executing following blocks of code. When we send a request to a server, it may take some time before we get a response. JavaScript keeps this process in “mind” and runs it in the background and goes on to execute the remaining codes. Even though this ability of JavaScript is very useful in some cases, we may not need it sometimes. Sometimes, we want to execute some piece of code only after something has happened. For instance, when we send a request to a server, we don’t want to go ahead and tell a user ‘Hello, we’ve received a response from the server!’, but rather, we would want to be sure we’ve received a response. How do we do that? We use callback functions to execute some piece of code only and only after the response is received.

Tip

I’ll be embedding pens from CodePen so we will be printing to the document body. We’ll write a print() function which creates a paragraph element (containing a specified value) and appends the paragraph element to the document body. I’ve explained it with comments:

// the print function 
function print(data) {

  // the new paragraph element
  let p = document.createElement('p');

  // set paragraph to contain value of argument “data”
  p.innerHTML = data;

 // append paragraph to the document body
  document.body.append(p);
}

So, henceforth, instead of using console.log(), we’ll use print().

Let’s take our first example:

Example

function func1(){
    print('1st Function');
}

function func2(){
    print('2nd Function');
}

func1();
func2();

From the above example, we realized that func1() is executed first, then func2(), as expected. What if func1() has some piece of code that will run only after sometime and we want func2() to run only and only after the func1() is done executing; we would need our func2 function to wait.

Example

function func1() {
    setTimeout(function(){
        print('From func1.');
    }, 2000);
}
function func2() {
    print('From func2.');
}
func1();
func2();

Click on the "Rerun" button to see the prints in realtime.

The func1() function has a setTimeout() function invoked within it which prints a text after 2 seconds. func2() is expected to run only after func1() is done running. But what happens? func2() runs before func1() and this is due to how JavaScript works; we don’t want that. Callback Functions to the rescue!

Creating a Callback Function

Callbacks can be used to solve this issue. A callback function can be created by:

  1. Creating a callback argument on the function definition
  2. Calling the callback after the function completes its necessary operation.

Let’s rewrite our func1-func2 code:

Example

// define the callback as an argument of the higher-order function
function func(callback) {
    setTimeout(function(){
       print('From func1.');

       // call the callback after function completes necessary operations
       callback();

    }, 2000);
}

// now let’s test our higher-order function and its callback
func( function() {
    print('From callback.');
});

In the above example, we define a function func() and pass an argument callback to it. The callback argument becomes a callback function after we mention it with the () at the end of the global setTimeout() function; invoking it. When we later invoke the func() function, the printing is done after 2 seconds before the callback is called. Now we see the results displaying in the right order. Sweet!

Tips:

  1. A callback function is a Closure. A closure is an inner function that has access to its outermost function’s local scope. Meaning a closure can use all objects defined in its outermost function’s definition block.
  2. We don’t always have to use the identifier callback as the name of the callback function. We can use any valid JavaScript identifier. It is good practice to name objects with their respective purposes that’s why using the identifier callback is a common practice.
  3. If we invoke the callback() after or before the setTimeout() function, the callback fires before the setTimeout() function’s result is displayed - then we wouldn’t have completed our mission. We called the callback at the end of the body of the setTimeout() function because that’s the point where our func() function finishes running.
  4. We can define an external function, and then pass its name (without the ()) to the func() function instead of using an anonymous function:
// an external function
function externalFunc(){
    print('I\'m external!');
}

// passing to func instead of an anonymous function
func(externalFunc);

Callback functions with arguments

Since a callback function is a closure, it means we can access the higher-order function’s local variables and any object in its local scope, as a whole. Sometimes it will be important to grab some data or functions and work with them after the function is done executing. In the example below, we make a fake request to a server to grab a user’s information, and then we use a callback to display some of this information on the document body.

Example

function getInfo (userID, callback) {
    let info = null;

    // we make the pseudo-request, based on the user ID
    setTimeout( function() {
        // now the data (response) is received as follows
        info = {
            name: 'Percy',
            friends: [ 'Joan', 'Fred', 'Cassy'],
            isActive: false
        };

        // we then pass the info as a parameter to the callback
        callback(info);
    }, 2000);
}

// we call the getInfo function and then do something else with the callback
getInfo('ehdjsd78934hdfkd', function (info) {
    // we now have access to the info object from the server response
    let status = info.isActive ? 'online' : 'offline';

    print(info.name + ' is ' + status + '.' );
});

From the example above, we parse a fake JSON data received from the pseudo-request and parse it into an actual object literal identified as info. After we grab the data from the server, we pass the object as a parameter to the callback function. This process allows us to access the info object in the callback function’s body. The ?: operator does a pretty simple job: if info.isActive evaluates to true, return ”online”, else return ”offline”. In our case the Boolean property is false hence ”offline” is returned.

Ensure callback is a function

It is advisable to check if the object passed as parameter to the higher-order function is indeed a function before calling the callback function.

Example

function foo(callback) {
    // … need-to-wait code block

    // check if callback is a function
    if (typeof callback === 'function') {
        callback();
    }
}

Doing this can help avoid yielding runtime errors.


This brings us to the end of our discussion. I hope this article serves as an eye-opener for those who’re new to callback functions. Do make use of the comment section to ask any question bothering your mind.

Comments (2)

Andrew Gupta's photo

so here i am having a question. Where should i ask mcdvoice.top ?

Gyen Abubakar's photo

An aspiring Fullstack Developer, currently mastering front-end. I love writing and teaching others the little I know. Join me on my journey.

just type it here