IT IS HERE! Get Smashing Node.JS on Amazon Kindle today!
Show Posts
← Back to homepage

I decided to write a little article to discourage an unfortunately common pattern in Node.JS modules (and browser JavaScript, to a lesser extent) that can boil down to these two examples:

// A: 
function myFunction () {
  if (somethingWrong) {
    throw 'This is my error'
  }
  return allGood;
}

and

// B: async Node.JS-style callback with signature `fn(err, …)`
function myFunction (callback) {
  doSomethingAsync(function () {
    // …
    if (somethingWrong) {
      callback('This is my error')
    } else {
      callback(null, …);
    }
  });
}

In both cases, passing a string instead of an error results in reduced interoperability between modules. It breaks contracts with APIs that might be performing instanceof Error checks, or that want to know more about the error.

Error objects, as we’ll see, have very interesting properties in modern JavaScript engines besides holding the message passed to the constructor.

The stack property

The fundamental benefit of Error objects is that they automatically keep track of where they were built and originated.

The mechanism of how this happens is specific to each JavaScript engine and/or browser that implements it.

V8 (Node.JS)

The way that V8 handles this, which directly affects us who develop in Node.JS is described by the StackTraceApi document.

We can test its behavior by simply initializing a Error object and inspecting its stack property:

// error.js
var err = new Error();
console.log(typeof err.stack);
console.log(err.stack);
∞ node error.js
string
Error
    at Object.<anonymous> (/private/tmp/error.js:2:11)
    at Module._compile (module.js:411:26)
    at Object..js (module.js:417:10)
    at Module.load (module.js:343:31)
    at Function._load (module.js:302:12)
    at Array.0 (module.js:430:10)
    at EventEmitter._tickCallback (node.js:126:26)

As you can see, even without throwing or passing it around, V8 is able to tell us exactly where that object was created, and how it got there.

By default, V8 limits the stack trace size to 10 frames. You can alter this by changing the Error.stackTraceLimit during runtime.

Error.stackTraceLimit = 0; // disables it
Error.stackTraceLimit = Infinity; // disables any limit

Custom Errors

If you wanted to extend the native Error object so that stack collection is preserved, you can do so by calling the captureStackTrace function. This is an example extracted from the Mongoose ODM

function MongooseError (msg) {
  Error.call(this);
  Error.captureStackTrace(this, arguments.callee);
  this.message = msg;
  this.name = 'MongooseError';
};

MongooseError.prototype.__proto__ = Error.prototype;

The second argument passed to the captureStackTrace prevents unnecessary noise in the stack generation by hiding the MongooseError constructor calls from the stack.

Beyond stack strings

As you might have noticed in my previous code example, I purposedly printed the typeof of the stack property. As it turns out, it’s a regular String with a format optimized for readability.

V8, much like Java, allows complete runtime introspection of the stack. Instead of a string, we can access an array of CallSites that retain as much information as possible about the function call in the stack, including the object scope (this).

In this example, we override the prepareStackTrace function to access this raw array and examine it. We call `getFileName` and other methods on each CallSite (all the available methods are described here)

function a () {
  b();
}

function b () {
  var err = new Error;

  Error.prepareStackTrace = function (err, stack) {
    return stack;
  };

  Error.captureStackTrace(err, b);

  err.stack.forEach(function (frame) {
    console.error(' call: %s:%d - %s'
      , frame.getFileName()
      , frame.getLineNumber()
      , frame.getFunctionName());
  });
}

a();

This is an example of the output this script produces:

∞ node error-2.js 
call: /private/tmp/error-2.js:3 - a
call: /private/tmp/error-2.js:23 - 
call: module.js:432 - Module._compile
call: module.js:450 - Module._extensions..js
call: module.js:351 - Module.load
call: module.js:310 - Module._load
call: module.js:470 - Module.runMain
call: node.js:192 - startup.processNextTick.process._tickCallback

All this functionality would of course be non-existent if we were to pass around strings, therefore drastically shrinking our debugging panorama.

For real-world usage, restoring the original prepareStackTrace after overriding it is probably a good idea. Thankfully, TJ Holowaychuck has released a tiny callsite module to make this painless.

Browsers

Like the V8 document states:

The API described here is specific to V8 and is not supported by any other JavaScript implementations. Most implementations do provide an error.stack property but the format of the stack trace is likely to be different from the format described here

  • Firefox exposes error.stack. It has its own format.
  • Opera exposes error.stacktrace. It has its own format.
  • IE has no stack.

A very interesting solution to this is provided by javascript-stacktrace, which attempts to normalize a printed stack trace across all browsers.

On IE (and older versions of Safari), for example, it uses a clever method: it recursively looks for the caller property of a function, calls toString on it and parses out the function name.

var currentFunction = arguments.callee.caller;
while (currentFunction) {
  var fn = currentFunction.toString();
  var fname = fn.substring(fn.indexOf(&amp;quot;function&amp;quot;) + 8, fn.indexOf('')) || 'anonymous';
  callstack.push(fname);
  currentFunction = currentFunction.caller;
}

Conclusions

  • When doing async I/O (in Node.JS or otherwise), since throwing is not a
    possibility (as it results in uncaught exceptions), Errors are the only way to
    allow proper stack trace collection.
  • Even in non-V8 browser environments, it’s probably a good idea to still initialize
    Errors. The API exists in all browsers, and the extended API facilities that V8
    provides are bound to be available to most engines in the future.
  • If you’re throwing or passing around strings for errors, consider switching today!

The examples in the beginning of the post can thus be rewritten this way:

// A: 
function myFunction () {
  if (somethingWrong) {
    throw new Error('This is my error')
  }
  return allGood;
}

and

// B: async Node.JS-style callback with signature `fn(err, …)`
function myFunction (callback) {
  doSomethingAsync(function () {
    // …
    if (somethingWrong) {
      callback(new Error('This is my error'))
    } else {
      callback(null, …);
    }
  });
}

19 Comments

Stephen Belanger said

+1

In production, I log errors with as much detail as possible. Without at least having the stack trace, the errors probably aren’t going to tell me anything when I read back through the logs.

Aeron said

I think the technique javascript-stacktrace uses only works with functions like “function tal()” not “tal = function” (which is still basically an anonymous function). That’s why it’s handy to wrap object inheritence in some kind of factory, just as an example, but like jQuery UI does. Then you can wire in method names and guarantee meaningful stack traces across all devices.

Guillermo Rauch said

@Aeron
I disagree. V8, SpiderMonkey and JavascriptCore have already implemented `Function#name`, which in your example can only be obtained if you use the `function tal()` syntax. If we want meaningful stack traces, we should all stick to that syntax, as opposed to new layers of abstraction.

Aeron said

hi Guillermo!

Hm, I didn’t know about `Function#name`, is that like this:
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/Name

But that still doesn’t support anonymous functions, unless you do something like:

`tal: function tal(){ … }`

Which is not very elegant. The approach I was talking about absolutely works for anonymous functions in that sense, you can see it here:

https://github.com/aglemann/js-oo/blob/master/lib/oo.js

The name is available as the `__name__` property. That javascript is basically the same inheritence model used in jQuery UI (both came from Resig’s “Simple JavaScript Inheritance” example). You can argue that JS native inheritance “works” and we shouldn’t wrap things in new layers of abstraction, but seems like all the major frameworks (jQuery UI, Backbone, Mootools, etc) disagree.

(Happy Holidays buddy!)

Eric Wendelin said

@Aeron: Actually, I do as much as I can to guess anonymous function names by getting the actual source line and figuring it out. However, this doesn’t work with minified JS and this feature was off by default until about 6 months ago.

@Guillermo: We can actually do “var func = function fnname()”, which is very verbose but will give us “fnname” for the Function#name.

Guillermo Rauch said

@Aeron @Eric
`function tal () { }` defines the function within the scope. Why would you do var a = function fnname` ?

Eric Wendelin said

@Guillermo: There are inconsistencies with how functions are handled when not declared by `var`. They are very minor IMO, but worth considering. See the “function oddities” section of https://developer.mozilla.org/en/JavaScript/Reference/Scope_Cheatsheet FWIW, I personally always use `function` and not `var`.

Guillermo Rauch said

Yep, I wasn’t aware exactly of all the specifics, but I always am careful with blocks and `if`s when defining my functions. Thanks for the cheat sheet link, though, that’s really useful.

Aeron said

@Eric: Very clever, wouldn’t have thought of that. But yeah, still not helpful in production environment. You probably want to be logging these errors as they occur with your users, which mostly likely deals with minified files (if you’re debugging yourself it’s doubtful you need a stacktrace).

@Guillermo: When you’re talking about large-scale JS, the only functions you are likely to declare with the named function syntax are private methods. The majority of your methods will be anonymous functions declared as object attributes. Anyway that’s been my experience. But it’s a common pattern with Backbone, jQuery, Mootools, etc. Anyway I’m not telling you anything you don’t already know, you write code like poetry and your socket.io – for example – is largely anonymous functions.

Aeron said

BTW the idea for highjacking the inheritance model with hooks for introspection came from this post:

http://javascriptweblog.wordpress.com/2010/06/01/a-tracer-utility-in-2kb/

Mariusz Nowak said

Apart of extending native Error object, you can also try more direct route and assign error type message to ‘type’ property. Internally V8 does that and it will probably become standard in Node.js: https://github.com/joyent/node/issues/1454

Dustin Diaz said

I saw this article pop up a while ago when you published it. Lol, totally guilty of this in some recent projects. Thanks for the heads up.

Aeron said

I updated JS-OO to include an example of what I mean (at the bottom of the README):

https://github.com/aglemann/js-oo

phree said

Reading above, I didn’t realize that var f = function() {} would sometimes rob your function name… here, I did some tests sampling different methods and viewing the value of stack

Rubens Mariuzzo (@rmariuzzo) said

Excellent! Each time I read an article from you, I learn something new.

Sam said

Cool post! Bookmarked it.

Domenic Denicola said

It would be great if you could replace `arguments.callee` with `MongooseError` in the “Custom Errors” section. `arguments.callee` is strict-mode-unfriendly, and IMO strict mode needs as much help as it can get :)

Your thoughts?

About Guillermo Rauch:

CTO and co-founder of LearnBoost / Cloudup (acquired by Automattic in 2013). Argentine living in SF.