DEV Community

Cover image for Arrow functions: a walkthrough and gotchas
Sylwia Vargas
Sylwia Vargas

Posted on • Updated on

Arrow functions: a walkthrough and gotchas

In this blog post I show how to transform a traditional function into a so-called 'arrow function'. I start with functions with two arguments, then cover no arguments and finally, one argument.
NOTE: I initially planned to cover also the gotchas but realized that this would make this blog post "about everything and nothing". Before I write another blog on the gotchas, check the last section of the blog to learn about the two main gotchas. Also, I just created a mini-quiz where you get prompts for transforming functions — check it out after this blog :)


Two arguments

  1. This is our function we want to transform:
    function sum(num1, num2){
        return num1 + num2
    }
    
  2. Arrow functions are anonymous so in order to preserve the name, we need a variable:
    const sum
    
  3. Now, put an = between the name and the arguments, and a => between the arguments and the curly brackets
    const sum = (num1, num2) => {
      return num1 + num2
    }
    
  4. This already works! However, since the body of the function has only line, we can write it like this:
    const sum = (num1, num2) => { return num1 + num2 }
    
  5. And now, since this is only one line, we can simplify it:
    const sum = (num1, num2) => num1 + num2
    

    WHAT?! NO RETURN?! Yes. Putting it simply, you need a return statement as soon as there are {} in the picture — and you need {} as soon as you have more than 1 line of function body.

  6. NOTE: DarkWiiPlayer provides a more detailed explanation that I thought fits this blog's purpose:

    You need {} when the function body is not just one expression, that is, when you need additional statements before the return value. You can easily write something like:

    const sum = (array) => array
       .reduce( (a,b) => a+b)
    

    but you can't do

    const sumPlusOne = (a, b) => a+=1; a+b
    

    No arguments

    If you have no arguments, here's how you can go about it. This is the function we want to be transformed:

    function helloWorld(){
     console.log("Hi")
    }
    

    you can make it into:

    const helloWorld = () => console.log("Hi") 
    

    or:

    const helloWorld = _ => console.log("Hi")  
    

    NOTE: the difference is that (_) marks to your developer colleagues that there might be some default argument and () says there will be no defaults you care about — but it’s a niche thing and practically no one uses it. For more information, check this comment by Kyle Roach.


    One argument

    When it comes to just one argument:

    function myName(name){
     console.log(`Hi, my name is ${name}`)
    }
    

    can be:

    const myName = name => console.log(`Hi, my name is ${name}`)
    

    since it’s just one argument, it doesn’t need parenthesis.


    Gotchas

    When it comes to arrow functions, suffice it to say for now that there are, really, two main gotchas:

    1. arrow functions saved to a variable are function expression and as such cannot be hoisted;
    2. arrow functions do not have their own binding of this and this represents an object in which the arrow function has that defined it.

    I just created a mini-quiz where you get prompts for transforming functions — check it out after this blog :)


    Cover picture by Pexels

Top comments (20)

Collapse
 
reedjones profile image
Reed Jones

const helloWorld = _ => console.log("Hi") doesn't really mean 'there will be for sure no defaults', in fact it can be used as a regular variable!

const double = _ => _ * 2
double(5) // 10
Enter fullscreen mode Exit fullscreen mode

its really no different than

const double = number => number * 2
Enter fullscreen mode Exit fullscreen mode
Collapse
 
sylwiavargas profile image
Sylwia Vargas

Yeah, it can be used as a variable but customarily, it seems to mean that there wouldn't be any default arguments. Codewise, it is not different - you are right in that.

Collapse
 
iroachie profile image
Kyle Roach

Customarily? Actually, if you use any linters like eslint or typescript, using _ will tell the developers that it does have an argument, while () shows as void and passing any argument will throw a warning.

See typescriptlang.org/play/index.html...

Thread Thread
 
sylwiavargas profile image
Sylwia Vargas

I wouldn't say TypeScript is a linter — did you mean TSLint?
I appreciate you bringing TS perspective but the blog post is entirely about just JS.

Historically, as linters are considered, the underscore parameters would trigger is declared but never used error, which was fixed in TypeScript 2.0. However, it does still trigger unused variable/parameter warning from a linter. As we all know, linters are not always correct or up to date.

As I said, it is a niche thing and I trust my colleagues who expressed such an opinion. Perhaps it is also a team-depending styling choice.

I heard that the difference is that if e.g. you run in the browser console now and paste

document.body.addEventListener("click", () => console.log(event))

this will still have an access to the default argument that's event, which would not be the case with _. However, trying this out with _ that works as well so maybe that's browser-dependent? In all honesty, I don't have the time now to track this down but will put it in the "parking lot" for the times when I can research it.

Also, it's fascinating that you've been on Dev for over two years and this is your first post or comment ever. I'm quite happy that my post evoked such strong feelings that you decided to respond!

Thread Thread
 
iroachie profile image
Kyle Roach • Edited

Sure, TypeScript isn't exactly a linter and this post isn't about TypeScript, but I would say that today a lot of js tooling is powered by typescript so it's not a far stretch.

But okay let's use JSDoc, which isn't typescript related. You can see it still generates _ to be expecting a variable.

mage

Thread Thread
 
sylwiavargas profile image
Sylwia Vargas

I'm happy you feel passionate about this subject and I appreciate the time you put into this (making the screen recording, converting it into a gif, uploading it). For now, I'll just repeat: "As we all know, linters are not always correct or up to date." I'd be curious what docs say about it if you wanted to actually check what the motivation/history is behind introducing the _ as a variable.

Thread Thread
 
iroachie profile image
Kyle Roach

_ as a variable is used when you want to ignore the first parameter when using a function, we sometimes call it a throwaway variable.

A great example in the Array.forEach method. Say we wanted to loop through items in an array and console out the index.

The first argument in the forEach callback is the item, and the second is the index. Because parameters are ordered in js, if we want to use the 2nd one, we have to provide a variable for the 1st one.

const names = ['Sylwia', 'Kyle'];

names.forEach((name, index) => console.log(index))

Since what we really need is the second variable, we can omit the first one with _.

names.forEach((_, index) => console.log(index))

So _ acknowledges there is a parameter there, but we're not using it in our implementation.

Here are two posts I found you can use for reading:

Thread Thread
 
sylwiavargas profile image
Sylwia Vargas • Edited

Awesome! Thanks. This is what I meant by

there will for sure be no defaults you care about

I was aware of the Wes Bos' post, which is where I encountered the _ back in the day. Since this is niche, produces warnings and seems to be treated differently by dev teams, I am quite curious about how the argument came to be and what the intention was behind it — perhaps the ES6 standards or discussion thread will shed some more answer.

Thanks for the research — I've included a mention of your comment in the blog :)

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

and you need {} as soon as you have more than 1 line of function body

Almost but not completely correct. You need {} when the function body is not just one expression, that is, when you need additional statements before the return value. You can easily write something like

const sum = (array) => array
   .reduce( (a,b) => a+b);

but you can't do

const sumPlusOne = (a, b) => a+=1; a+b
Collapse
 
sylwiavargas profile image
Sylwia Vargas • Edited

Good catch! I've edited the post to include your comment and attributed it to you :)

Collapse
 
jeromegamez profile image
Jérôme Gamez

A nice and concise overview and explanation on how to migrate to arrow functions, thank you!

Collapse
 
sylwiavargas profile image
Sylwia Vargas

Thank you, Jérôme! I appreciate you taking the time to comment not to correct but to show appreciation. I think the world would be a better place if we took more time to give shout outs as a habit :)

Collapse
 
jeromegamez profile image
Jérôme Gamez

Reading some other comments before posting mine this was exactly what I thought 😅.

Thread Thread
 
sylwiavargas profile image
Sylwia Vargas

Yeah, we still have a long way to go as a community if any time you write an intro blog post, you'd get a 10x dev feeling personally offended that you didn't include something way more advanced 😂

oh well, thanks for being a wholesome human!

Collapse
 
latobibor profile image
András Tóth • Edited

I recommend proof-reading the article as you have left in some typos and unfinished sentences:
e.g. NOW RETURN?! and it’s a niche thing and no one uses it even in obscure dev <- this sentence is not finished.

When discussing the arrow functions it is important to mention that it does not have its own this. So you can't bind these types of functions. Personally, I think you should use anonymous functions for small throwaway functions. Whenever you need reusable stuff you should aim to use the real, normal functions.

Also one more thing might have been mentioned and that is returning an object without the return keyword:

myArray.
  map(item => ({
    prop1: item.prop1 + item.prop2,
    prop2: item.prop3
  })
);

Or even adding destructuring to the mix:

myArray.
  map(({ prop1, prop2 }) => ({
    anotherProp1: prop1,
    prop2,
  })
);
Collapse
 
sylwiavargas profile image
Sylwia Vargas • Edited

Hi Andras! Thank you for taking the time to write this. I initially planned to talk about this, hoisting and function expressions, execution context, etc. but realized that:

  1. this blog post is a beginning of a series called "js warm-ups" and is meant as supplement to helping people understand basic js concepts in a simple way;
  2. the blog post was getting long and therefore, potentially overwhelming for people trying to just understand what the hell arrow functions are.

I decided to split it into two (or more), this one addressing the direct need of some of my students (expressed yesterday) and the other will be published when I have time to finish it later this week. Sadly, I entitled the blog before I finished it. Just bear with me :)

I did edit the post to include the two main gotchas, though.

Collapse
 
latobibor profile image
András Tóth

You are right, for beginners it must be scary. That's I think an inherent problem with the learning curve of JS: first it's very flat, then there's a huge uphill mountain to grasp all the quirks and gotchas (around function pointers, this and objects, immutability of Promises) and then it's very flat again.

Collapse
 
ajax27 profile image
Shaun Collins

Great explanation for arrow functions Sylwia :)

Collapse
 
mayankjoshi profile image
mayank joshi

So, is it necessary for the variable to be constant?

Collapse
 
juanicolas profile image
JuaNicolas

No, it doesn't, but otherwise you can set another value to the variable.