5

I just learned that, writing this:

const setName = name => {
  // set name here
}

is bad as opposed to this

function setName(name) {
  //set name here
}

The reason was that the first approach makes it difficult to debug issues related to that function since it will not appear in the stack trace.

Question: Why would this not appear in the stack trace?

Kunj
  • 1,980
  • 2
  • 22
  • 34
Siya Mzam
  • 4,655
  • 1
  • 26
  • 44
  • 1
    The function will appear in the stack trace, it will just be named `(anonymous)` which is unhelpful. – evolutionxbox Feb 13 '18 at 10:07
  • "is bad as opposed to this" what do you mean by **bad**? Show complete example where arrow function didn't appear in stack trace. – Yury Tarabanko Feb 13 '18 at 10:07
  • It does not appear in the error stack because you assign anonymous function to a variable. This is the same thing as `const f = function() {//something impressive};` – oniondomes Feb 13 '18 at 10:08
  • where did you see that 'it will not appear in the stack trace. '? – xianshenglu Feb 13 '18 at 10:08
  • 1
    @oniondomes: It *does* appear in the stace trace, and the first function in the question [is not anonymous](https://stackoverflow.com/questions/27977525/how-do-i-write-a-named-arrow-function-in-es2015/37488652#37488652). (Neither is the one in your comment, but the one in your comment is not the same as the one in the question.) – T.J. Crowder Feb 13 '18 at 10:13
  • @t-j-crowder, I see how I was wrong. Almost. Could you explain how the one in my comment is not anonymous? – oniondomes Feb 13 '18 at 10:23
  • 1
    @oniondomes: For the same reason the arrow function isn't; see the link in that comment for details. As of ES2015, functions get assigned names in *lots* of ways other than a declaration or named function expression. One of those ways is assigning the freshly-created function to a variable or constant. The function gets its name from the variable or constant (so the OP's function is called `setName`, and yours is called `f`). Those names will appear in stack traces, and as the `.name` property on the function. – T.J. Crowder Feb 13 '18 at 10:30
  • Also, even it were anonymous, the stack trace would still show the location of the line of code in the source, so it's still traceable. – CherryDT Jan 23 '23 at 07:59

2 Answers2

30

Are Arrow Functions Untraceable?

No, they're just as traceable as any other function, if you're referring to being able to debug them, stack traces, etc.

I just learned that, writing this...is bad as opposed to this...

No, it isn't. It's different. It's not objectively bad

The reason was that the first approach makes it difficult to debug issues related to that function since it will not appear in the stack trace.

Yes it will. If someone told you it won't, they're mistaken — which is understandable, because arrow functions don't have a declaration syntax, and their expression syntax doesn't have anywhere to put a name; so it seems reasonable to assume they don't get names. While some don't, many, many do (see my answer here for details on how and when), including yours. Let's see the name in a stack trace:

const setName = name => {
    throw new Error("Ack!");
};

const wrapper = name => setName(name);

wrapper("foo");
Look in the real console.

If you run that, you'll see this in the console:

Uncaught Error: Ack! at setName (js:14) at wrapper (js:17) at js:19

Notice how both setName and wrapper are listed.

So even functions created with anonymous syntax can (somewhat paradoxically) get names, and often do. This isn't just something that a specific JavaScript engine does to be helpful (though it used to be), it's dictated by the JavaScript specification.

So what is anonymous function syntax vs. named function syntax? Let's quickly review five common ways to create functions (this isn't a full list — I provide one here — just five common ones):

  • A function declaration (the ^ markers indicate the declaration itself, the markers indicate the contents of the function):

        function example1() { return 42; }
    // ^^^^^^^^^^^^^^^^^^^^^−−−−−−−−−−−−^
    
  • A named function expression (relatively rare):

    const example = function example2() { return 42; };
    //              ^^^^^^^^^^^^^^^^^^^^^−−−−−−−−−−−−^
    
  • An anonymous function expression:

    const example3 = function() { return 42; };
    //               ^^^^^^^^^^^^−−−−−−−−−−−−^
    
    
  • A "verbose" arrow function expression (has a function body block, no implied return):

    const example4 = () => { return 42; };
    //               ^^^^^^^−−−−−−−−−−−−^
    
  • A "concise" arrow function expression (no function body block, implied return):

    const example5 = () => 42;
    //               ^^^^^−−−
    

function declarations always include a name (with one obscure exception²). function expressions can be named or anonymous. All arrow function expressions are anonymous. But all of the functions in those examples have names (example1, example2, example3, example4, and example5). Their names are assigned based on the expression they're a part of. (See the answer linked earlier for details.)

To be clear: Some arrow functions don't have names, just like some traditional functions don't have names. For instance, neither of these functions has a name:

result = theArray.map(s => s.toUpperCase());
result = theArray.map(function(s) { return s.toUpperCase(); });

The only advantage with regard to names that traditional functions have over arrow functions that their expression syntax lets you specify a name explicitly rather than relying on context, like this:

result = theArray.map(function upperCaseCallback(s) { return s.toUpperCase(); });

There's no way to do that in a single expression with an arrow function, you'd need something like this instead:

const upperCaseCallback = s => s.toUpperCase();
result = theArray.map(upperCaseCallback);

But even though some arrow functions are anonymous, your example isn't.


¹ In fact, there are some arguments the other way: The arrow function doesn't have a prototype property and an object associated with it, and calling it doesn't require setting up a new this binding or arguments pseudo-object and binding (since arrows don't have their own this, arguments, or [where relevant] super). So in theory, it's lighter-weight. (That said, V8 [at least, and probably others] optimizes those things, for instance by only creating the object for prototype if it's actually used.) Countering that is some question of readability — "I didn't realize that was a function" (though I suspect people will get used to arrow functions) — and the hoisting behavior of function declarations is sometimes useful and has no arrow analogue.

² "function declarations always include a name (with one obscure exception²)." The obscure exception is this:

export default function() { }

That's an exported function declaration, not a function expression, with hoisting and such. It's the only place a function declaration can have no name specified. (The function does get a name, though: default.)

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 3
    I find this really helpful. Thank you very much @T.J, you taught me something. I really appreciate. – Siya Mzam Feb 14 '18 at 08:51
  • @T.J, seems like there are exceptions where arrow functions won't have names. I tried to document the case in an answer below. Any chance you could weigh in and make sure I'm not missing something? Thanks for this answer and any review you can do below! : ) – Steve Ladavich Apr 08 '20 at 21:37
5

Whoever told you this may be under the impression that your arrow function doesn't have a name. They're mistaken about that, too.

This doesn't always seem to be the case. "Anonymous" functions only get named when they are assigned to a variable.

const f = () => () => { debugger; };
const h = f();
const g = () => { debugger; };


console.log(f.name) // "f"
console.log(h.name) // ""
console.log(g.name) // "g"


h();
g();

Calling h() has this stack trace:

enter image description here

Whereas calling g() has this stack trace:

enter image description here

Note how "g" is named and "h" just says "anonymous".

So if you're defining partially applied function the way the f is written in the above example, you might run into the problem that some people are talking about.

I imagine it will mostly be an issue if you're relying on someone else reporting a bug to you and sending you their stack trace since if you hit if while developing you'll be able to click into to it and just look at it.

In some example, you might be able to use the line number to get you there, but that might be more of a problem if someone is giving you the stack trace per some bundled code.

You can avoid this by naming the function "inside" of your other function...

const f1 = () => {
  const named = () => { debugger; };
  return named;
}

// or

const f2 = () => { 
  return function named() { debugger; }
};
Steve Ladavich
  • 3,472
  • 20
  • 27
  • I didn't say that **all** arrow functions have names. A lot of them don't, such as just about any arrow function you supply as an inline callback. I said that the OP's arrow function has a name, which it does. :-) The question was specifically about two styles of creating functions, assigning to a constant vs. using a function declaration. – T.J. Crowder Apr 09 '20 at 07:12
  • 1
    @T.J.Crowder fair enough! I was kind of reading your answer as a "no worries! there are no gotchas with arrow functions!" (if you'd even call this a "gotcha") which is maybe extrapolating what you said one step too far! Thanks for the reply – Steve Ladavich Apr 09 '20 at 13:39