We know functions are building blocks of any program. We can use our beloved concise syntax of arrow function expressions since 2015, but as with everything we should know and understand their superpowers and limitations. In this post, we will delve into their caveats and nuances.
The syntax
Arrow functions are always anonymous, so the basic syntax consist a pair of parenthesis, an arrow and expression.
You can also use rest parameters, default parameters, and destructuring. In these cases parenthesis for params are required.
It’s also working with asynchronous code
You can also assign them to a variable and give it a name to make them more reusable
The body, as you probably noticed, can be either an expression body, where you can omit curly braces and an explicit return statement (this way it will always return an expression, and only one expression can be specified). Or can be a usual block body, where you can put multiple statements in curly braces. This way the arrow function by default returns undefined, and you have to explicitly provide the return value.
You are probably wondering, what if I want to use the expression body, as more concise, but I want to return an object. Well, you won’t get the result you want, instead your function will return undefined. Why? Because JS can only understand the expression body if there is no left brace after the arrow. We can simply fix this by wrapping our object literal in parentheses.
As you can see the syntax of arrow functions is super compact and readable. Especially with body expressions, you can omit some boilerplate for traditional function expressions, and save some development time.
What about bindings?
Here comes the first limitation of arrow functions. They don’t have their bindings for ‘this’, ‘arguments’, or ‘super’, so you really shouldn’t be using them as methods.
Problems with ‘this’
You can feel temptation, however, to use them in classes. The class’s body has its own ‘this’ context, so using the arrow function as class field would correctly point to an instance of the class, or class itself (if used as static fields), because it is a closure for that field.
But there are two caveats of using ‘auto-bound methods’ as arrow functions are named in this context. First of all, because it’s a closure and not binding of the method itself, the value of ‘this’ won’t change based on the execution context. This can lead to some unexpected behaviors in your program. Second of all, you have to remember that fields are defined on the instance of the class, not on its prototype. So every time you create an object based on such a class, the new function reference is also created and gets new closure, potentially leading to memory leaks.Here is a proper way of dealing with the 'this' keyword in traditional functions used as class fields:
Problems with ‘arguments’
Arrow functions do not have their own ‘arguments’ object. However, they will take the arguments of the outer scope if called for.
We can use rest parameters tho as an alternative to arguments object, and it works as expected.
What about using as constructor?
Arrow functions will throw a TypeError when called with the ‘new’ keyword, so there is no point in using them as constructors.
What about using it as a generator function?
Arrow functions will throw a SyntaxError if you try to use the ‘yield’ keyword in their body. The only way you can do that is to nest a traditional generator function inside the arrow function.
Proper generator function
Where those arrows shine?
Shorter syntax for anonymous functions
As we stated before, especially with expression body, arrow functions are very simple and readable alternatives to traditional anonymous functions. Having an implicit return makes this solution even cleaner.
Dealing with array methods
Arrow functions provide a very concise way of writing array methods, enclosing them in one line of readable code:
Dealing with callbacks for event listeners
Some of their characteristics can be both pros and cons. While using them as class fields could potentially lead to bugs when dealing with state, they are a great solution as a callback for event listeners, as their ‘this’ context comes from the outer scope of the class.
Summary
I hope I could provide some insights into when and where you can leverage arrow functions effectively, and where the caution is needed. They can be a way to write concise, readable, reusable code. Having their limitations in mind will help you avoid errors and frustration in the future.
