Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Damon Gentry
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Damon Gentry@Fuseboxer )

Anonymous Functions, Assigned To References, Show Up Well In JavaScript Stack Traces

By Ben Nadel on

The other week, as I was reading Programming JavaScript Applications by Eric Elliott, I came across a passage that didn't feel right. Or, at least felt outdated - the book was written 3 years ago. On pages 31-32, Eric talks about "anonymous functions" and how they show up in error stack traces with little helpful information. While this is definitely true for function expressions defined as part of a high-order function invocation, the book seemed to indicate that this behavior is also true for function expressions that are assigned to variables and object properties. This may have been true at the time of the writing; but, this is definitely not [or no longer] true in modern browsers.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

First, let's take a look at the passage from the book, Programming JavaScript Applications:

The disadvantage is function expressions create anonymous functions unless you explicitly provide a name. Anonymous functions are used in JavaScript frequently - with reckless abandon, perhaps. Imagine that you've declared all of your functions this way, and you have a pile of functions that call functions that call even more functions. This is a common scenario in a well-architected, event-driven application. Now imagine that you're 12 function calls deep and something goes wrong. You need to debug and view your call stack, but looks something like this:

(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)

Obviously, this is not very helpful.... The baz example exhibits the same behavior. It's another anonymous function assigned to a property in an object literal. (Pages 31-32)

I am sure we can all interpret this differently; but, to me, this seemed to indicate that function expressions, assigned to variables or object properties, show up as strictly anonymous in stack traces. To test this, I created a simple AngularJS page that consumes function expressions assigned to both local variables and well as object properties:

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Anonymous Functions, Assigned To References, Show Up Well In JavaScript Stack Traces
  • </title>
  • </head>
  • <body>
  •  
  • <h1>
  • Anonymous Functions, Assigned To References, Show Up Well In JavaScript Stack Traces
  • </h1>
  •  
  • <p>
  • <em>See console, that's where the sweet action is</em>.
  • </p>
  •  
  • <!-- Load scripts. -->
  • <script type="text/javascript" src="../../vendor/angularjs/angular-1.3.15.min.js"></script>
  • <script type="text/javascript">
  •  
  • // I run after the AngularJS application has been bootstrapped and configured.
  • angular.module( "Demo", [] ).run(
  • function demo() {
  •  
  • // I am an anonymous function expression assigned to a variable.
  • var doThis = function() {
  •  
  • doThat();
  •  
  • };
  •  
  • // I am an anonymous function expression assigned to a variable.
  • var doThat = function() {
  •  
  • var x = y;
  •  
  • };
  •  
  • // I am an anonymous function expression assigned to a property.
  • var thing = {
  • oneTimeKickIt: function() {
  •  
  • doThis();
  •  
  • }
  • };
  •  
  •  
  • // NOTE: I have to invoke the functions after they are defined because
  • // they are expressions, not declarations, and are therefore not hoisted"
  • // to the top of the current function block.
  • try {
  •  
  • // Using an anonymous function expression with no named-variable
  • // assignemnt to demonstrate stack notation.
  • (function() {
  •  
  • thing.oneTimeKickIt();
  •  
  • })();
  •  
  • } catch ( error ) {
  •  
  • console.log( error.stack );
  •  
  • }
  •  
  • }
  • );
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, the call stack starts inside a truly anonymous function (immediate invoked), which calls an object property, which calls a function variable, which calls another function variable, which throws an error. When we catch the error, we log the following stack trace:


 
 
 

 
 Function expressions, assigned to references, show up properly in JavaScript stack traces. 
 
 
 

As you can see, the stack trace properly identifies the functions by the name of the reference (whether it be a variable of an object property). I tested this in Firefox, Chrome, Safari, and also tested the non-AngularJS portion in Node.js - all of them properly represented the anonymous function in the call stack.

NOTE: If you provide an additional, explicit name in the function assignment (ex, var foo = function bar(){}), the provided name [bar] is what shows up in the stack trace.

I'm happy that modern browsers have finally solved this problem. Having to provide an additional name for each function assignment would be a huge pain in the butt. That said, you should still provide an explicit name for functions defined as part of a higher-order function invocation or an immediately invoked function expression - those still show up poorly in stack traces.




Reader Comments

You're right, but that's not the whole story.

The reason new browsers handle this correctly is because in ES6 (which browsers are starting to support), some function names can be inferred. Of course, this won't be the case on most mobile browsers for a long time to come.

Sadly, for object literal methods and concise method syntax, the name gets set properly in the callstack, but you won't be able to use it for recursion, and (at least in Babel), you won't get proper tail calls by referencing `this.methodName()`. Sad but true.

In other words, if you need to reference the function from within a method, you'll still want to manually add the name. There's a more detailed explanation and a handy ESLint rule to alert you and help you figure out what to do when you encounter that problem here: https://github.com/johnstonbl01/eslint-no-inferred-method-name

Finally, for lambdas (the functions you pass into higher order functions), you'll still want to include a name if the function is more than one line long.

So named function expressions will still have a place, even in ES6.

Reply to this Comment

Nice, have noticed this improving recently but this clears things up for me - especially with Eric's explanation.

I just don't understand why the demo is using Angular?

Reply to this Comment

@Eric,

Interesting - I didn't realize that it was tied to an ES6 feature - I just thought was how the browsers were seeing the code. It also didn't occur to me that mobile browsers would be behind the times. I have often thought that mobile browsers were typically ahead of the curve. That said, clearly I don't do a lot of mobile development ;)

@Hazza,

Re: AngularJS - I think I had started the demo intending to render some UI updates based on the error. But, then, as the demo rounded out, I decided to just use the console. You are correct in not seeing any connection between the features being explored and AngularJS.

Reply to this Comment

Sorry for this late post, but as I find the topic interesting, here are my 2 cents around it all. =)

Personally I have not quite understood what you actually gain by defining functions using function expressions. Apart from being sensitive to order with regards to forward references they just make the whole code so much harder to read. At least in my (slow) brain, I like to be able to quickly scan the contents of a file to get an initial fast overview of its contents. When functions are declared using function declarations they clearly stand out from the other "var" declarations and I can more rapidly come to grips with what actually will be variable in the code. Unless you have some logic where a variable during execution actually will point to different functions in a dynamic way, defining functions with function expressions really does not make much sense to me. This is especially true when you also can pass around functions declared with function declarations by reference to other functions.

Is there really any situation where a function expression is a better method than a standard function declaration? I am not being negative with this post. I am only trying to see if I can learn something new about all this. =)

Best regards,

Fedde

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.