Asynchronous Adventures in JavaScript: Callbacks

Benjamin Diuguid
DailyJS
Published in
6 min readJan 19, 2016
http://thecodebarbarian.com/2015/03/20/callback-hell-is-a-myth

Incase you missed it, in the last Asynchronous Adventure, I covered Understanding the JavaScript Event Loop. Now let’s move on to the topic of callbacks. We couldn’t start talking about callbacks until we talk about how to define functions in JavaScript.

Functions

In other C-like languages (Java, C#, C++, etc.) you may be familiar with Class’s having methods or functions within their bodies. But in JavaScript Functions are first-class objects. This makes the concept of passing around functions, and invoking functions very simple and intuitive. If you’re new to JavaScript, let’s take a quick look at how we declare functions:

Defining Functions

Here we see an example of defining and invoking a few functions that can do various things. Aside from looks Arrow Functions act a little different, but that’s another topic for another time. Find out more here.

Synchronous Example

Without Callbacks

In this example, we are manually using a for loop to iterate through every element in the array and printing each array element with the console.log API.

This may be simple enough for doing one thing, but what if we wanted to do something else within the body of the for loop. What if we wanted to perform some other action on every element within the array.

One way would be to write a new loop calling our other function.

for(i = 0; i < myArray.length; i++) {
consume(myArray[i]);
}

In this example, we have some external function consume, which we are using to perform our desired action. But implementing this desired feature can get out of hand quickly if we want to iterate over the array several times and perform different actions on each iteration, or even if we want to change the way the array or structure is being iterated over.

Enter Callbacks

Let’s create an abstraction over the portion of the code that is iterating through every element in an array. Let’s use functions to accomplish this task:

Now we have our consumeEach function which takes in two arguments. The first parameter, arrayToConsume, is simply the array that is passed into our function that will be iterated over and the desired action will be performed on each element of it. The second parameter, consumeFunction, is the desired action that we wish to perform on each element. So to console.log every element we can now write this piece of code:

Native Abstraction

Fortunately iterating over an array in JavaScript is something that is done frequently, so this abstraction is built into the language, and a function forEach is built into JavaScript Arrays.

With higher level abstractions like this is is possible to easily change the underlying iterating implementation without having to change this core business logic. This is often desirable because it provides a level of flexibility and reusability, along with a central place for possible performance optimizations if there is a need.

Asynchronous Examples

Here are where things can get potentially confusing. Thinking back to my last article on Understanding the Event Loop, we will sometimes want to make calls to functions that could take a long time to return. If we call these functions in a synchronous manner, they will block anything else from running.

We overcome this issue by using built in Browser, or Node API’s which will offload the potentially long call, to a different thread. In order to perform actions when this long call is finished running, we will provide this asynchronous call with a function, which will be invoked/called back upon completion of the an asynchronous task.

setTimeout

The setTimeout function, works in both the Browser and in Node, and takes in two parameters. A function, which will be called back at an appropriate time, and a time (in milliseconds) which indicates about how long to wait before invoking/calling the provided callback function. In the next example, what do you think gets logged first?

Let’s break down what happens in this gist:

  1. We call setTimeout with an anonymous/inline function, and a time of 5,000 milliseconds.
  2. The Browser/Node sends this to an external API (The C++ layer which is the piece that is actually multithreaded)
  3. Because this is a special asynchronous API, we continue chugging in the JavaScript
  4. I think I’m done!” gets logged.
  5. Five seconds later, the “Actually Done!” function is put into the callstack queue, and when we are done executing the current call, we invoke the callback function and “Actually Done!” Is logged.

If you’ve coded in other languages, this may not look very intuitive at first. Let’s take a look at a few more examples.

HTTP Requests

Here we are making some kind of asynchronous HTTP request to some server. Again we pass an anonymous function which takes 2 parameters, errorReturned, and dataReturned. Here we simply assign the variable requestData to the value given back to us by our asynchronous request through the callback parameter dataReturned, when the asynchronous request finishes.

Let’s look at one more example.

Reading the File System

Here we are using the Node core module fs (file system) to read in the contents of a file named ‘myFile.txt.’ Upon the completion of reading the file, our anonymous callback function is called and is passed the data. We then process this data with our processData function.

Why not use return values?

What if we did this in JavaScript…

var data = fs.readFile(‘myFile.txt’);
processData(data);

To answer this we must again look at how JavaScript executes code.

JavaScript is single-threaded by nature and thus can only do one thing at a time. Let’s say that this readFile call takes 30 seconds to read the entire file. Let’s also pretend we’re running this code in a Node server and some piece of our API calls and hits the above code. What happens?

Our server will be unable to do anything else but wait for the readFile call to return, thus rendering our server useless for an entire 30 seconds. Instead, what we want to do is kick off the long readFile call in the background and continue to process any other requests that we receive.

So because we don’t know how intensive that this readFile call could be, we use it in an asynchronous manner (really it is provided to us in an asynchronous manner using the Node C++ API layer) and give it a callback function. Giving the readFile a callback function, is basically like saying “Hey when you have the data, let me know by invoking this function, passing in the appropriate values.”

In JavaScript terms, when the readFile finishes executing in the external Node C++ layer it passes the appropriate values to the function and places this function in the callback queue. Then when the JavaScript finishes its current function call, and is able to pick up its next call, it will look to the callback queue, and thus invoke our anonymous callback function and process the data!

Common Practices

In Node and some front end libraries the callback function will usually take multiple parameters. As a common practice the first parameter is the error object/value if an error occurred. If no error occurred, this will be passed into the callback as null usually.

asyncCall(function(error, value) {
if(error) {
// Handle the error.
} else {
// Do something with the value.
}
});

The second (or third, fourth, fifth, etc.) parameter(s) will then be the desired return value(s).

Callback Hell

As powerful and awesome as callbacks are, there are potential drawbacks. The most common is often termed “Callback Hell.

In this example, we need to make multiple asynchronous calls, after other asynchronous calls finish, so we must nest these calls within callbacks. This code will very quickly grow towards the right, and start to look like a tree.

The Remedy

There are a lot of ways to refactor code like this, many of which will be described in future Asynchronous Adventures. An immediate still callback based approach to flatten this nested callback structure is a library conveniently named async.

Stay Tuned

Please join me in this series of Asynchronous Adventures where I will be highlighting how to handle asynchronous code in JavaScript from the basic stuff, to the advanced, fancy strategies.

This series covers in-depth analysis (with examples!) of the following JavaScript patterns:

Ben Diuguid is currently a student at the University of Florida. He is incredibly passionate about creating things, especially with JavaScript.

--

--