Naming JavaScript Functions

Remember that anonymous function? Probably not. After all, if it wasn’t worth a name, it probably hasn’t been used since.

But written one lately? Absolutely. They abound, especially in event handlers:

document.addEventListener('DOMContentLoaded', function () {
  console.log('ready!');
});

And iterators:

[1,2,3,4].filter(function (n) {
  return (n % 2) === 1;
});

Most of the time they’re, ahem, functionally harmless. But there are good reasons to give them a name.

Reuse

We recognize that the iterator above checks for odd parity. More complicated functions won’t be as clear, though. So rather than making a reader work back through the actual implementation, why not describe its behavior with a name?

function isOdd (n) {
  return (n % 2) === 1;
}

[1,2,3,4].filter(isOdd);

This isn’t just clearer to read. By providing a name (or assigning to a variable) we’ve turned a one-off function into something we can reuse throughout a project. If we wanted to write a function for choosing prime numbers from a list, a first step might be to use isOdd to filter out anything divisible by two. Now that it’s named, we can do that.

Clarity

Besides allowing us to reuse functions throughout a project, names can also help us understand what they represent. If we can establish a consistent convention we can guess at a glance how a function should be used. Consider the following rules:

  • is*, has* (isChocolate, hasFrosting) – truth tests around an object property
  • to* (toJSON) – a conversion to another type
  • get* (Cake.prototype.getType) – retrieves a property from an object
  • set* (Cake.prototype.setType) – sets a property on an object

If we adhere to convention, we will immediately know that we can use isOdd to filter collections of things; based on context we can make a reasonable inference that those things will be numbers and only odd ones will be returned.

Debugging

Stack traces indicate sources of error, but they’re infinitely more useful when the functions that are failing have names. For instance, running a purely-anonymous function with node’s --stack-trace-limit set to 1:

(function () {
  throw new Error('Whodunnit?');
})();

Produces an unhelpful trace:

Error: Whodunnit?
    at repl:2:7

Contrast with the result once a name has been added:

(function isJudgeDoom () {
  throw new Error('Whodunnit?');
})();

Error: Whodunnit?
    at isJudgeDoom (repl:2:7)r>

Much better.

Trace summary

Each interpreter will present traces slightly differently, but all benefit from more information. Using the Node REPL as an example, contrast an error in an anonymous function with the following:

DefinitionTrace

var rotten = function () {
  throw new Error('!!!');
};

Error: !!!
  at rotten (repl:2:7)

Foo.prototype.rotten = function () {
  throw new Error('!!!');
};

Error: !!!
  at Foo.rotten (repl:2:7)

Foo.prototype.rotten = function evil () {
  throw new Error('!!!');
};

Error: !!!
  at Foo.evil [as rotten] (repl:2:7)

The best course, then, is to err on the side of caution: name early, name often.

Do it for clarity, do it for reuse. Do it to support debugging. But unless the function is the most trivial one-off, just do it.

Featured