DEV Community

Cover image for The Async Series: Callbacks
Nicholas Mendez
Nicholas Mendez

Posted on • Edited on • Originally published at thecodecrusade.wordpress.com

The Async Series: Callbacks

Introduction

One of the most infamous gotchas in JavaScript to beginners is the idea of asynchronous programming. This series is all about demystifying async in JavaScript.

What does Async mean?

Most of the code we write execute sequentially or synchronously. That is execute line 1 then line 2 then line 3.

For example, the following are all synchronous function calls. You can only perform one of them at a time. If you are showering then you can safely assume the wake up task has been fully completed.

wakeup()
eatBreakfast()
takeShower()
Enter fullscreen mode Exit fullscreen mode

However, in JavaScript some function calls are executed concurrently. This means the browser would actually switch between them so they appear to make progress simultaneously.

For example: from the moment you wake up in the morning, you start seeing. Then, you may also start moving and at some point you will eat. You might even do all three at once! Note, even though you started seeing before you started eating, the eating task will be completed before the seeing one. This in essence, is how asynchronous function calls behaves.

see()
move()
eat()
Enter fullscreen mode Exit fullscreen mode

Because of this, a series of asynchronous function calls may finish their execution in a different order than they were started. Often this can be a point of frustration to new programmers when a particular order is desired.

Why does this happen?

While it is not a hard requirement, if you'd like to get an appreciation as to why this happens in the first place; you can take a look at my post on the JavaScript runtime.

Async Code in Action

I have three asynchronous functions printBlue/Green/Red. They will all be executed concurrently but each has a different execution time. Green is fastest then red then blue. So if they are called like this.

printBlue("Blue");
printGreen("Green");
printRed("Red");
Enter fullscreen mode Exit fullscreen mode

Output:

image

The output is: Green, Red, Blue. The diagram below visualizes the timing of these functions.

image

Getting into flow

Because of their individual execution speeds, the functions finished in a different order than they were called printing "Green", "Red" then 'Blue". However, we want to print "Blue", "Green" then "Red" how do we fix this?

In programming, control flow is the concept of enforcing the order of execution of statements. In JavaScript, one way in which we give the developer the ability to achieve this is to have our asynchronous functions accept Callback Functions.

A callback function is a function C that we pass to another function A (often async) such that C is executed by A only when the main work of A is completed.

Function A is also said to be a higher-order function due to the fact that it has another function as a parameter.

Luckily our print functions do accept callback functions so we can create some helper functions that does our desired printing. Then, pass our helper functions as callbacks.

//helper function 1
function doPrintRed(){
  //calls print red with our desired parameter
  printRed('Red');
}

//helper function 2
function doPrintGreenRed(){
  //calls printGreen with our desired parameter 
  //then passes doPrintRed as a callback
  printGreen('Green', doPrintRed);
}

//call printBlue then passes do doPrintGreenRed as a callback
printBlue("Blue", doPrintGreenRed);
Enter fullscreen mode Exit fullscreen mode

However, this is needlessly long. As we have no use for our helper functions outside of passing them as callbacks, we can make use of anonymous functions instead.

Anonymous functions are nameless function definitions that can be written anywhere a function can be referenced. For example instead of writing doPrintRed we can provide an anonymous function in doPrintGreen.

//helper function 2
function doPrintGreenRed(){
  //calls printGreen with our desired parameter 
  //replace reference to doPrintRed with an anonymous function
  printGreen('Green', function(){
      //calls print red with our desired parameter
      printRed('Red');
  });
}

//call printBlue then passes do doPrintGreenRed as a callback
printBlue("Blue", doPrintGreenRed);

Enter fullscreen mode Exit fullscreen mode

We moved doPrintRed's code into an anonymous function and passed it to printGreen as a callback. Hence the second parameter of printGreen is called an anonymous callback function.

You can then repeat the process for doPrintGreenRed to attain the following.

//replace reference to doPrintGreenRed with an anonymous function
printBlue("Blue", function(){
   //calls printGreen with our desired parameter 
   printGreen("Green", function(){
     //calls print red with our desired parameter
     printRed("Red");
   });
});
Enter fullscreen mode Exit fullscreen mode

The callback function passed printBlue() calls printGreen(). printGreen() in turn also receives a callback function which then calls printRed(). printBlue/Green/Red are designed in a way such that the callbacks they receive are only executed after printing to the screen.

This is the output:

image

The execution now looks like this.

image

This is because the innermost function must wait on an outer function to execute, and that outer function must wait on another outer function to begin execution.

Writing Higher-Order Functions

It is up to the author of the asynchronous function to design the function to accept a callback, execute the callback with an appropriate value and explain it via documentation.

Below is a simple implementation of a higher-order add function that receives a callback function.

function add(a, b, callback){//allow a callback to be passed
  let answer = a + b;//perform calculation
  callback(answer);//pass the output of the calculation to the callback
}

add(4, 5, function(result){
 console.log("The result is", result);
});//Output: The result is 9

Enter fullscreen mode Exit fullscreen mode

The function add() computes a sum and passes the result to a function parameter passed to add(). Higher-order functions such as add() may not return a result but instead ask for a function to pass the result to.

The example code is available at this REPL so you can try it out yourself.

Conclusion

And that concludes the intro into asynchronous JavaScript. Many JavaScript APIs are asynchronous including fetch(). Getting to grips with this fundamental concept would serve you well on your journey.

Top comments (3)

Collapse
 
anaekin profile image
Animesh Jain

Its not a good practice to use multiple callbacks as it can lead to callback hell and error catching could also be an issue. Its better to use promise chaining for synchronisation.
Also, async/await is the best option to handle the async operations synchronously.

Collapse
 
snickdx profile image
Nicholas Mendez

True! Those alternatives will be explored later in the series.

Collapse
 
ali_hassan_313 profile image
Ali Hassan

Well served explanation