TOC
Functions:

Callback functions

Functions in JavaScript are so-called First-class citizens, which essentially means that a function can be stored in a variable, returned as the output from another function or passed into a function as an argument. This is a very powerful feature that you won't find in all programming languages.

In this article, we'll discuss the part about passing a function as an argument to another function. When doing so, we usually refer to the passed-in function as a callback function. This can be a very useful technology, and in fact, JavaScript uses it quite a lot internally as well, with several of the built-in objects having functions that can accept a callback function.

We already had a glimpse of callback functions in the previous article, where we talked about anonymous functions, because a callback function is sometimes created as an anonymous function. It doesn't have to be though, but it's often practical because the function used as a callback function might not be useful anywhere else.

So, now that we now what a callback function is, let's try working with them!

Passing in a callback function

As mentioned, JavaScript will accept a callback function for many of the methods found on built-in objects. A great example of this is the Array object, which uses callbacks quite a bit, e.g. for the Array.filter() method. The filter() method will allow us to create a filtered version of an existing array, where an item is only included if the passed-in callback function approves of it.

So, lets say that you have an array of fruits but you only want the fruits starting with the letter A. We can then create a function that decides whether a fruit starts with A or not, and return true or false depending on the outcome, like this:

function StartsWithA(fruit)
{
	return fruit[0] == "A";
}

This simple function just checks the first letter of the passed-in fruit, returning true if it's the letter "A" - otherwise it returns false. Now this function can be passed to the filter() method, which will call it for each item in the array and return a new array based on which fruits passed the test. Here's the complete example:

function StartsWithA(fruit)
{
	return fruit[0] == "A";
}

let fruits = ["Apple", "Orange", "Pineapple", "Avocado"];

let fruitsStartingWithA = fruits.filter(StartsWithA);

// Result: Apple,Avocado
alert(fruitsStartingWithA);

Notice how I can simply pass in the StartsWithA function as a parameter to the filter() method just by referencing its name - JavaScript really makes it easy to use callback functions!

Now as mentioned, if you don't think you'll need the StartsWithA() function for other purposes, you can pass it as an anonymous function instead, leaving the global space with one less function to clutter things up, and possibly making your code more readable:

let fruits = ["Apple", "Orange", "Pineapple", "Avocado"];

let fruitsStartingWithA = fruits.filter(function(fruit)
{
	return fruit[0] == "A";
});

// Result: Apple,Avocado
alert(fruitsStartingWithA);

As you can see, in the call to the filter() method, I pass in a function created on-the-fly, without a name, since it will only be used for this specific purpose. This is shorter, but it can be even shorter, thanks to arrow function expressions, which would probably be a more suitable approach in this case, where our function is just a one-liner anyway:

let fruits = ["Apple", "Orange", "Pineapple", "Avocado"];

let fruitsStartingWithA = fruits.filter(fruit => fruit[0] == "A");

// Result: Apple,Avocado
alert(fruitsStartingWithA);

Short, sweet and simple, and while it might not look much like our initial example, we are indeed still using a callback function - only the syntax has changed.

Creating and using callback functions

From the examples above, you can see that passing in a callback function to an existing function is very simple, but is it just as simple to create a function that accepts and uses a callback function? Actually, yes - it's really easy! First of all, let's see how we can define a function with a callback parameter:

function FunctionA(callback)
{
	callback();
}

function FunctionB()
{
	alert("Hello from FunctionB!");
}

FunctionA(FunctionB);

In this example, FunctionA is the main function, accepting a callback as a parameter. FunctionB will be the callback function passed to FunctionA. Because functions are first-class citizens in JavaScript, a parameter that accepts a callback function looks just like any other parameter, and can be called from the receiving function like any other function.

So, with that in place, let's try creating a more illustrative example. We'll create our own array-filtering function, which will accept a callback function and then we'll let the callback function decide whether an item can be included in the final result or not. Here's how it may look:

function FilterArray(array, callback)
{
	let result = [];
	for(let item of array)
	{
		if(callback(item))
			result.push(item);
	}
	return result;
}

Our filter method will accept an array as the first parameter, and a callback function as its second parameter. It will then loop through the array, and for each item in it, it will call the supplied callback function and pass the item to it. The callback function should then return true or false, depending on whether it considers the item worthy of being included.

Now the cool thing is that you can get completely different behavior from the filter function simply by supplying your own, custom callback function. Let's give it a try:

let fruits = ["Apple", "Orange", "Pineapple", "Avocado"];

function FilterArray(array, callback)
{
	let result = [];
	for(let item of array)
	{
		if(callback(item))
			result.push(item);
	}
	return result;
}


function StartsWithA(fruit)
{
	return fruit[0] == "A";
}

function HasLongName(fruit)
{
	return fruit.length > 5;
}

// Apple,Avocado
alert(FilterArray(fruits, StartsWithA));
// Orange,Pineapple,Avocado
alert(FilterArray(fruits, HasLongName));

As you can see, I have used the StartsWithA() function from before, and then added a HasLongName() function. They use different logic to decide if an item is relevant. In the last lines of the example, you can see how I call the same FilterArray() function, but because I use different callback functions, I also get different results from it. So instead of having the logic in the filter function, I have made the function more generic by allowing it to receive the logic from an external source, through a callback function.

Asynchronous operations & callbacks

Another situation where callbacks are really useful is when you're dealing with asynchronous operations. A very common example is when you want your (client-side) JavaScript code to interact with resources on your server, e.g. fetch some data generated on-the-fly, perhaps based on user input.

When doing so, you will usually make an asynchronous call to the server - this will allow the browser to continue the execution of your code instead of stopping to wait for the server to respond. But sometimes, you have code that depends on the server call to be finished before continuing, and for situations like that, callbacks are really useful.

Let's have a look at an example. If we didn't have callbacks, it might look something like this:

function DownloadFile(url)
{
	console.log("Downloading file...");
	setTimeout(function()
	{
		console.log("File downloaded - ready for processing!");
		return "/local-file.png";
	}, 2000);
}

function ProcessFile(path)
{
	console.log("Processing file: " + path);
}

let path = DownloadFile("https://www.google.com/logo.png");
ProcessFile(path);

We have a function for downloading a file. I use the setTimeout() function to fake a call to an asynchronous download function, to make the example less complex. Once the file is "downloaded", we return the local path to the caller. After the call to the DownloadFile() function, we call the ProcessFile() function, including the local path to the file.

If you run this example, two major problems will be quite obvious: We can't really return the local path from the DownloadFile() function, because the function is exited as soon as the asynchronous call has been made and therefore it doesn't return anything. And because we don't wait for the DownloadFile() function to finish before calling ProcessFile(), we're actually trying to process it before the file has been fully downloaded, as illustrated by the output:

"Downloading file..."
"Processing file: undefined"
"File downloaded - ready for processing!"

Let's fix this, by using a callback instead - here's the modified version of the example:

function DownloadFile(url, callback)
{
	console.log("Downloading file...");
	setTimeout(function()
	{
		console.log("File downloaded - ready for processing!");
		callback("/local-file.png");
	}, 2000);
}

function ProcessFile(path)
{
	console.log("Processing file: " + path);
}

DownloadFile("https://www.google.com/logo.png", ProcessFile);

This fixes both our problems - we simply pass the ProcessFile() function as a callback to the DownloadFile() function, which will call it when the file has been downloaded and even pass in the local path. Try running the example and you will see from the output that everything now works as expected:

"Downloading file..."
"File downloaded - ready for processing!"
"Processing file: /local-file.png"

We have now solved our problem by using a callback, and the code is even more elegant if you ask me.

Summary

Because functions in JavaScript are First-class citizens, we are allowed to pass a function, as a parameter, to another function. When doing so, we refer to the first function as a callback function, and as illustrated in this article, callbacks are very useful in several situations.


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!