Function callbacks

One of the most powerful features of JavaScript and, in fact, the technology that Node was built on, is the concept of callback functions. A callback function is a function that is passed into another function, and is then generally invoked inside the function. This is how asynchronous programming is achieved. By supplying a callback function, we are saying to the function we are calling, go and do what you need to do, and then when you are finished, call this function. Just as we can pass a value into a function, we can also pass a function into a function.

This is best illustrated by taking a look at some sample JavaScript code:

var callbackFunction = function(text) { 
    console.log('inside callbackFunction ' + text); 
} 
 
function doSomethingWithACallback( initialText, callback ) { 
    console.log('inside doSomethingWithCallback ' + initialText); 
    callback(initialText); 
} 
 
doSomethingWithACallback('myText'', callbackFunction);

Here, we start with a variable named callbackFunction that is a function that takes a single parameter. This callbackFunction simply logs the text argument to the console. We then define a function named doSomethingWithACallback that takes two parameters, initialText and callback. The first line of this function simply logs "inside doSomethingWithACallback" to the console. The second line of the doSomethingWithACallback is the interesting bit. It assumes that the callback argument is in fact a function, and invokes it, passing in the initialText variable. If we run this code, we will get two messages logged to the console, as follows:

inside doSomethingWithCallback myText
inside callbackFunction myText

This output clearly shows that we enter into the doSomethingWithACallback function, log a message to the console, and then invoke the callbackFunction.

But what happens if we make a mistake, and do not pass a function as a callback when we should? There is nothing in the preceding code that signals to us that the second parameter of doSomethingWithACallback must be a function. If we inadvertently called the doSomethingWithACallback function with two strings, as shown in the following code snippet:

doSomethingWithACallback('myText', 'anotherText'); 

We would get a JavaScript runtime error:

TypeError: callback is not a function  

Defensive-minded JavaScript programmers, however, would first check whether the callback parameter was in fact a function before invoking it, as follows:

function doSomethingWithACallback( initialText, callback ) { 
    console.log('inside doSomethingWithCallback ' + initialText); 
    if (typeof callback === "function") { 
      callback(initialText); 
    } else { 
          console.log(initialText + ' is not a function !!') 
    } 
} 
doSomethingWithACallback('myText'', ''anotherText'');

Note the third line of this code snippet, where we check the nature of the callback variable before invoking it. If it is not a function, we then log a message to the console. The output of the code snippet would be as follows:

inside doSomethingWithCallback myText
anotherText is not a function !!

JavaScript programmers, therefore, need to be careful when working with callbacks. Firstly, they need to code around the invalid use of callback functions, and secondly, they need to document and understand which parameters are, in fact, callbacks.

What if we could document our JavaScript callback functions in our code, and then warn users when they are not passing a function when one is expected? The type annotations for callback functions will do this for us, and generate compile errors when we break these rules. Let's take a look at how this is accomplished in TypeScript.