Why you should keep using old style JavaScript functions!
By: Jake Ginnivan
Tags: JavaScript, Es2015, Typescript

A pattern we heavily adopted when picking up ES2015 was const myFunction = () => {} rather than function myFunction() {} .

We recently have updated our code style guide to start using functions again in a number of scenarios.

Why functions

They are named in stack traces

There are a few caveats to this point, the ES2015 spec means const myFunction = () => {} will be named, Babel implements this, but it causes issues in the TypeScript compiler so its transpiled as an anonymous function. The issue is being tracked here.

Improved tooling

We use VSCode, using function directly lights up a few editor features which are not available when using arrow functions assigned to consts.

Because the editor can easily identify a function certain refactorings become available, like move to new file.

Editor actions with a function

Editor actions for arrow function assigned to const

Also when finding a symbol or using intellisense you get feedback that you are referencing a function instead of a variable.

Go to symbol, function vs variable

Intellisense, function vs variable

They are not block scoped

ES2015 brought with it block scoping, both var and function get ‘hoisted’ to the top of the current scope by the parser. This means that when using const arrow functions you MUST declare those functions before any code which references them. For example:

/**

* Using function - Works

**/ init()

function init() {} /**

* Using const arrow function

* Does not work - parser error

* 'Cannot access 'init' before initialization'

**/ init()

const init = () => {}

/** Using var arrow function

* Does not work - runtime error

* 'init is not a function'

**/ init()

var init = () => {}

This is because the parser will find all functions and var’s then initialise them for that scope. This means you can extract little well named util functions out of the important code, and keep them in the module you are working in.

When scanning the module code, the most important code is at the top of the module rather than a collection of little functions which are less important than the main exported function of the module.

Debugging

Many debugging tools cannot set breakpoints inside arrow function expressions, so first you have to convert it to a arrow function with a block body. Not the end of the world, but it’s easy to forget to add the return statement when you do this and can introduce bugs quite easily when making this sort of change.

When to use arrow functions

Arrow functions are extremely useful in a number of places, this is a few of the reasons which you would choose an arrow function over a traditional JavaScript function.

You are accessing 'this'

Arrow functions are special because they do not have this bound to their scope. This means you can access the this of a parent scope without having to resort to the old var self = this hack.

You are specifying the type of the function

For React components we want the entire function to be a particular type.

const MyComponent: React.FC<Props> = () => <div />

There is no way to say a function is a particular type with TypeScript currently. Instead you type the arguments and optionally the return type. Other scenarios where this is useful is express const myHandler: express.Handler = (req, res) => ...

Callback expressions

Arrow functions allow expressions as well as blocks for their body. The expression syntax is much more terse than a traditional function.

onChange={function(e) { setState({text: e.target.value }) }} // vs onChange={e => setState({ text: e.target.value })}

For reference an arrow function expression is simply when you do not give the arrow function body a block.

// Block arrow function

e => { setState({ text: e.target.value })} // Expression arrow function

e => setState({ text: e.taget.value })

If you are doing this, a nice hack when debugging is to use the || with a console.log to keep it an expression. e => console.log(e) || setState({text: e.target.value })

Summary

Do not forget the good old function, arrow functions are great but they should not replace functions which work better in many cases.