Capture and Report JavaScript Errors with window.onerror

onerror is a special browser event that fires whenever an uncaught JavaScript error has been thrown. It's one of the easiest ways to log client-side errors and report them to your servers. It's also one of the major mechanisms by which Sentry's client JavaScript integration (raven-js) works.

You listen to the onerror event by assigning a function to window.onerror:

At first glance the Error object isn't very special. It contains 3 standardized properties: message , fileName , and lineNumber. Redundant values that already provided to you via window.onerror.

The valuable part is a non-standard property: Error.prototype.stack. This stack property tells you at what source location each frame of the program was when the error occurred. The error stack trace can be a critical part of debugging. And despite being non-standard, this property is available in every modern browser.

Here's an example of the Error object's stack property in Chrome 46:

"Error: foobar\n at new bar (<anonymous>:241:11)\n at foo (<anonymous>:245:5)\n at <anonymous>:250:5\n at <anonymous>:251:3\n at <anonymous>:267:4\n at callFunction (<anonymous>:229:33)\n at <anonymous>:239:23\n at <anonymous>:240:3\n at Object.InjectedScript._evaluateOn (<anonymous>:875:140)\n at Object.InjectedScript._evaluateAndWrap (<anonymous>:808:34)"

Hard to read, right? The stack property is actually just an unformatted string.

Here's what it looks like formatted:

Error: foobar
at newbar(<anonymous>:241:11)
at foo(<anonymous>:245:5)
at callFunction(<anonymous>:229:33)
at Object.InjectedScript._evaluateOn(<anonymous>:875:140)
at Object.InjectedScript._evaluateAndWrap(<anonymous>:808:34)

Once it's been formatted, it's easy to see how the stack property can be critical in helping to debug an error.

There's just one snag: the stack property is non-standard, and its implementation differs among browsers. For example, here's the same stack trace from Internet Explorer 11:

Not only is the format of each frame different, the frames also have less detail. For example, Chrome identifies that the new keyword has been used, and has greater insight into eval invocations. And this is just IE 11 vs. Chrome — other browsers similar have varying formats and detail.

Luckily, there are tools out there that normalize stack properties so that it is consistent across browsers. For example, raven-js uses TraceKit to normalize error strings. There's also stacktrace.js and a few other projects.

window.onerror has been available in browsers for some time — you'll find it in browsers as old as IE6 and Firefox 2.

The problem is that every browser implements window.onerror differently, particularly, in how many arguments are sent to the onerror listener and the structure of those arguments.

Free Node eBook

Build your first Node apps and learn server-side JavaScript.

📧

Nice!

Check your email to confirm your subscription.

Here's a table of which arguments are passed to onerror in most browsers:

Browser

Message

URL

lineNo

colNo

errorObj

Firefox

✓

✓

✓

✓

✓

Chrome

✓

✓

✓

✓

✓

Edge

✓

✓

✓

✓

✓

IE 11

✓

✓

✓

✓

✓

IE 10

✓

✓

✓

✓

IE 9, 8

✓

✓

✓

Safari 10 and up

✓

✓

✓

✓

✓

Safari 9

✓

✓

✓

✓

Android Browser 4.4

✓

✓

✓

✓

It's probably not a surprise that Internet Explorer 8, 9, and 10 have limited support for onerror. But you might be surprised that Safari only added support for the error object in in Safari 10 (released in 2016). Additionally, older mobile handsets that still use the stock Android browser (now replaced with Chrome Mobile), are still out there and do not pass the error object.

Without the error object, there is no stack trace property. This means that these browsers cannot retrieve valuable stack information from errors caught by onerror.

Because JavaScript is single threaded, you don't need to use wrap everywhere — just at the beginning of every new stack.

That means you'll need to wrap function declarations:

At the start of your application (e.g., in $(document).ready if you use jQuery)

In event handlers (e.g., addEventListener or $.fn.click)

Timer-based callbacks (e.g., setTimeout or requestAnimationFrame)

For example:

$(wrapErrors(function(){// application startdoSynchronousStuff1();// doesn't need to be wrappedsetTimeout(wrapErrors(function(){doSynchronousStuff2();// doesn't need to be wrapped}));$('.foo').click(wrapErrors(function(){doSynchronousStuff3();// doesn't need to be wrapped}));}));

If that seems like a heck of a lot of work, don't worry! Most error reporting libraries have mechanisms for augmenting built-in functions like addEventListener and setTimeout so that you don't have to call a wrapping utility every time yourself. And, yes, raven-js does this too.

Okay, so you've done your job — you've plugged into window.onerror, and you're additionally wrapping functions in try/catch in order to catch as much error information as possible.

There's just one last step: transmitting the error information to your servers. In order for this to work, you'll need to set up some kind of reporting web service that will accept your error data over HTTP, log it to a file and/or store it in a database.

If this web service is on the same domain as your web application, just use XMLHttpRequest. In the example below, we use jQuery's AJAX function to transmit the data to our servers:

If you've made it this far, you now have all the tools you need to roll your own basic error reporting library and integrate it with your application:

How onerror works, and what browsers it supports

How to use try/catch to capture stack traces where onerror is lacking

Transmitting error data to your servers

Of course, if you don't want to bother with all of this, there are plenty of commercial and open-source tools that do all the heavy-lifting of client-side reporting for you. (Psst: you might want to try Sentry to debug JavaScript.)

Jobs!

If you're trying to build and iterate on your app using JavaScript error tracking, you might be suffering from a common affliction: noisy, low-value errors that make it hard to identify high-priority issues....