Named callback functions in JavaScript

As explained in the MDN Web Docs, a callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action. I’d like to share a nice little trick you can do when passing in a named function.

But first, let’s look at an anonymous callback function, since these are (unfortunately) the most common.

Here’s a rather contrived example: imagine we wanted to listen for all click events on the page, and log to the console the element which was clicked.

We might use an anonymous function, like so:

// Listen for all click events on the body element
document.body.addEventListener("click", function(event) {

  // Log the clicked element to the console
  console.log(event.target);

}, false);

…But please try not to do this. I highly recommend you read Todd Motto’s fantastic post, Avoiding anonymous JavaScript functions, to see what I’m talking about.

Instead, let’s move this into a named function, logElement:

/**
 * Log to the console the element which fired the event
 * @param {Object} event The Event object
 */
function logElement(event) {
  console.log(event.target);
}

Here’s where our nice little trick comes in. You might think we should call our new logElement function like so…

// Listen for all click events on the body element
document.body.addEventListener("click", function(event) {

  // Log the clicked element to the console
  logElement(event);

}, false);

…But this is actually superfluous. If you’re passing in a named function, you can just do the following, which is much cleaner:

// Listen for all click events on the body element
// Log the clicked element to the console
document.body.addEventListener("click", logElement, false);

You may be wondering how we can use the logElement function without passing the event argument to it. Remember that we’re simply passing in the name of a function we want to call at a later time—we’re not actually calling the function there and then, hence the lack of parentheses.

The event argument gets passed in automatically because of the way the EventTarget.addEventListener() method works. As explained in the MDN Web Docs:

The event listener can be specified as either a callback function or an object that implements EventListener, whose handleEvent() method serves as the callback function.

The callback function itself has the same parameters and return value as the handleEvent() method; that is, the callback accepts a single parameter: an object based on Event describing the event which has occurred, and it returns nothing.

In other words, any callback function you pass to the .addEventListener() method automatically gets the Event interface passed in as an argument.

This works for any method which expects a callback function that accepts one or more arguments. Let’s look at another example: the Array.prototype.forEach() method.

Let’s say I’m making a fruit salad. I have a shopping list of various fruits, which I’ve stored in an array called fruits:

var fruits = [
  "Strawberry",
  "Apple",
  "Mango",
  "Banana",
  "Pear"
];

The callback function you pass to the .forEach() method accepts between one and three arguments: currentValue (the current element being processed in the array), index (the index of currentValue in the array), and array (the array .forEach() was called upon).

I could loop through this array by passing in an anonymous function to log the name of each fruit and its index in the array…

// Log the name of each fruit and its index
fruits.forEach(function(fruit, index) {
  console.log(index, fruit);
});

…which would log the following to the console:

0 "Strawberry"
1 "Apple"
2 "Mango"
3 "Banana"
4 "Pear"

This works, but again, let’s avoid using an anonymous function. Instead, we’ll move this into its own named function (which we could then re-use on any array), called logItem:

/**
 * Log the index and value of the current element being processed in an array
 * @param {*}      value    The current element
 * @param {Number} index    The index of the current element
 */
function logItem(value, index) {
  console.log(index, fruit);
}

We can now achieve exactly the same result as before while making our call to the .forEach() method much more readable:

// Log the name of each fruit and its index
fruits.forEach(logItem);

Hopefully this gives you a nice idea of how you can make your code cleaner and more readable by using named callback functions. And please do have a look at the Todd Motto post I linked to. It’ll give you more insight into why it’s a good idea to avoid anonymous functions in JavaScript.