When you should not use JavaScript fat arrow functions


Code readability is extremely important. By some estimates, we’ll spend significantly more of our time as developers reading and mentally parsing code rather than writing it. Arrow functions can both improve and hurt readability. It is very important that we make choices that optimize our code for readability. I’m going to assume for the sake of this article is that the readers are using some form of JavaScript modules with a single module per file. The goal of the module, when it comes to readability, is to make it easy for a person to understand what that particular module does, and the interface it provides for use in other modules.

In JavaScript, ES2015 (i.e. ES6) introduced the fat arrow function syntax, and it is great … for some use cases. But I keep seeing developers recommending and using arrow function assignment to a const variable as a substitute for using function declaration. I think that is a problem and contributes to poor readability in most JavaScript files that use that pattern.

Let’s back up a bit and define some of these terms, and then we can talk about arrow functions, where they are great and where traditional functions can be used to improve readability.

 

Function Declaration

Using the function keyword to declare a function. Functions declared in this manner get their own scope (and also close over the enclosing scope). They will be hoisted to the top of their containing scope. Unlike variables declared with var that get hoisted though, functions declared and hoisted in this manner are callable immediately, regardless of where they are declared in the code scope.

function myFunc() {}

 

Function Expression

Using an assignment to assign an anonymous or arrow function to a variable. We used const here to ensure the variable cannot be re-assigned. The two statements are different, mainly in how they treat the this keyword.

const myFunc1 = function() {}
// or
const myFunc2 = () => {}

 

Arrow Functions

Arrow functions can be awesome. And assigning it with the const keyword does provide a benefit that means the function cannot be redeclared or overwritten. One of the best uses for arrow functions is in simple callback methods, like map, sort, and reduce. My personal rule of thumb is to move to a declared function once an arrow function exceeds 2 lines, although individual circumstances can warrant exceptions.

const doubledArray = originalArray.map((arrItem) => arrItem * 2);

But, when arrow functions start to extend to multiple lines, they can start to create problems when it comes to the readability of the code in my opinion. An assignment via const must come before those variables can be used in a module. Some of you may see where we’re going here.

 

The Problem

The main core of JavaScript modules can often be pushed down out of sight by the const assignments that must be done prior to being able to be used.

const _utility1 = () => {
  // multiple lines of code here
};
const _utility2 = () => {
  // multiple lines of code here
};
const action1 = () => {
  // multiple lines of code here, which use _utility1() and _utility2()
};

export { action1 }

If the example above, the interface for this module is via the action1 function, which uses the 2 utility functions. Because they are being assigned to const variables, they need to be declared prior to exporting them for use outside the module. The important information for someone reading this module isn’t the implementation of the utility1 or utility2, and they likely don’t even need to know about the specific implementation of the action1 function. They need to know that they can use action1 outside of this module.

 

Using function declarations (and good names) to solve the problem

A fairly simple rewrite to take advantage of function hoisting, along with high quality names, can make the file much more scannable and ensuring that a developer reading the code gets the information they need without having to read any extraneous code first. They can always dive into the individual functions as needed, but when opening the file for the first time, it is very clear what is going on.

export { wellNamedFn }

function wellNamedFn() {
  // multiple lines of code here, which use _utility1() and _utility2()
}
function _utility1() {
  // multiple lines of code here
}
function _utility2() {
  // multiple lines of code here
}

By utilizing the function hoisting feature of JavaScript, the isolation that modules provide (meaning there is not a concern that the function would get redefined),  the code can be arranged so that the most important information is front and center when a developer opens the file to read the code.

I’ll leave off with a hello world example, a React Component that has a utility function (often many utility functions) before the render function.

class HelloWorld extends React.Component {
const getGreetingSubject = () => 'world';

render() {
return <h1>Hello {getGreetingSubject()}!</h1>
}
}

vs

class HelloWorld extends React.Component { 
render() {
return <h1>Hello {getGreetingSubject()}!</h1>
}
}

function getGreetingSubject() {
return 'world';
}

It may seem relatively contrived in these small examples, but in real world cases, the irrelevant code can be significant and cause readability issues. By considering what is most important in each module, high readability can be ensured by focusing on making those important pieces of code very clear.