Capture and Report JavaScript Errors with window.onerror

Share this article

Capture and Report JavaScript Errors with window.onerror

Key Takeaways

  • window.onerror’ is a browser event that fires when an uncaught JavaScript error is thrown, making it a simple way to log client-side errors and report them to your servers. It is supported by all modern browsers, with varying implementation details.
  • The Error object passed to ‘window.onerror’ is valuable for debugging as it contains the error stack trace, which provides a source location for each frame of the program when the error occurred. However, the stack property is non-standard and its implementation varies among browsers.
  • While ‘window.onerror’ is widely supported, not all browsers pass the Error object, which means they cannot retrieve the stack trace property. This limitation can be worked around by wrapping code in a try/catch to capture the error and its stack property.
  • After capturing errors, the final step is transmitting the error information to your servers. This requires setting up a reporting web service that will accept your error data over HTTP, log it to a file and/or store it in a database. If the service is on a different domain, it will need to support Cross Origin Resource Sharing (CORS).

This article was created in partnership with Sentry. Thank you for supporting the partners who make SitePoint possible.

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:

window.onerror = function (msg, url, lineNo, columnNo, error) {
  // ... handle error ...

  return false;
}

When an error is thrown, the following arguments are passed to the function:

  • msg – The message associated with the error, e.g. “Uncaught ReferenceError: foo is not defined”
  • url – The URL of the script or document associated with the error, e.g. “/dist/app.js”
  • lineNo – The line number (if available)
  • columnNo – The column number (if available)
  • error – The Error object associated with this error (if available)

The first four arguments tell you in which script, line, and column the error occurred. The final argument, Error object, is perhaps the most valuable. Let’s learn why.

The Error Object and error.stack

At first glance the Error object isn’t very special. It contains 3 standardized properties: message, fileName, and lineNumber. Redundant values that are 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 new bar (<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 has 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:

Error: foobar
   at bar (Unknown script code:2:5)
   at foo (Unknown script code:6:5)
   at Anonymous function (Unknown script code:11:5)
   at Anonymous function (Unknown script code:10:2)
   at Anonymous function (Unknown script code:1:73)

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 similarly 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.

Browser Compatibility

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.

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.

Polyfilling window.onerror with try/catch

But there is a workaround — you can wrap code in your application inside a try/catch and catch the error yourself. This error object will contain our coveted stack property in every modern browser.

Consider the following helper method, invoke, which calls a function on an object with an array of arguments:

function invoke(obj, method, args) {
    return obj[method].apply(this, args);
}

invoke(Math, 'max', [1, 2]); // returns 2

Here’s invoke again, this time wrapped in try/catch, in order to capture any thrown error:

function invoke(obj, method, args) {
  try {
    return obj[method].apply(this, args);
  } catch (e) {
    captureError(e); // report the error
    throw e; // re-throw the error
  }
}

invoke(Math, 'highest', [1, 2]); // throws error, no method Math.highest

Of course, doing this manually everywhere is pretty cumbersome. You can make it easier by creating a generic wrapper utility function:

function wrapErrors(fn) {
  // don't wrap function more than once
  if (!fn.__wrapped__) {
    fn.__wrapped__ = function () {
      try {
        return fn.apply(this, arguments);
      } catch (e) {
        captureError(e); // report the error
        throw e; // re-throw the error
      }
    };
  }

  return fn.__wrapped__;
}

var invoke = wrapErrors(function(obj, method, args) {
  return obj[method].apply(this, args);
});

invoke(Math, 'highest', [1, 2]); // no method Math.highest

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 start
  doSynchronousStuff1(); // doesn't need to be wrapped

  setTimeout(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.

Transmitting the Error to Your Servers

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:

function captureError(ex) {
  var errorData = {
    name: ex.name, // e.g. ReferenceError
    message: ex.line, // e.g. x is undefined
    url: document.location.href,
    stack: ex.stack // stacktrace string; remember, different per-browser!
  };

  $.post('/logger/js/', {
    data: errorData
  });
}

Note that, if you have to transmit your error across different origins, your reporting endpoint will need to support Cross Origin Resource Sharing (CORS).

Summary

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 window.onerror works, and what browsers it supports
  • How to use try/catch to capture stack traces where window.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.)

That’s it! Happy error monitoring.

Frequently Asked Questions (FAQs) about Capturing and Reporting JavaScript Errors with window.onerror

How does window.onerror function in JavaScript?

The window.onerror event in JavaScript is a global event handler that is triggered when an error occurs while executing JavaScript code. It’s a powerful tool for debugging and error handling because it can capture and report uncaught exceptions or runtime errors. When an error occurs, the window.onerror function is called with five parameters: the error message, the URL where the error occurred, the line number, the column number, and the Error object.

What is the difference between window.onerror and try-catch in JavaScript?

Both window.onerror and try-catch are used for error handling in JavaScript, but they work in different ways. The try-catch statement lets you handle errors directly by wrapping the code that might throw an error inside a try block, and then responding to the error in a catch block. On the other hand, window.onerror is a global event handler that is triggered when an uncaught runtime error occurs. It’s a more general error handling mechanism that can catch errors that slip through your try-catch blocks.

How can I use window.onerror to capture JavaScript errors?

To use window.onerror, you need to define a function that will be called when an error occurs. This function should take five parameters: the error message, the URL where the error occurred, the line number, the column number, and the Error object. Inside this function, you can handle the error in whatever way you see fit. For example, you might log the error to the console, send a report to a server, or display a message to the user.

Can window.onerror capture all types of JavaScript errors?

While window.onerror is a powerful tool for error handling, it does have some limitations. It can capture most uncaught runtime errors, but it can’t catch exceptions that are thrown and caught within a try-catch block. Also, some types of errors, like network errors or CORS errors, may not trigger the window.onerror event.

How can I use the Error object in window.onerror?

The Error object is the fifth parameter passed to the window.onerror function. It contains information about the error that occurred, including the error message, the name of the error, and a stack trace. You can use this object to get more detailed information about the error, which can be helpful for debugging or error reporting.

What is a stack trace in JavaScript?

A stack trace is a report that provides information about the execution path of the program at the time an error occurred. It shows the sequence of function calls that led up to the error, which can be very helpful for debugging. In JavaScript, you can get a stack trace from the Error object.

How can I get a stack trace in JavaScript?

In JavaScript, you can get a stack trace from the Error object. When an error occurs, an Error object is created and passed to the window.onerror function. This object has a property called stack, which contains the stack trace. You can access this property to get the stack trace.

Can I use window.onerror to report errors to a server?

Yes, you can use window.onerror to report errors to a server. Inside your window.onerror function, you can send a request to a server with information about the error. This can be useful for monitoring and debugging purposes, as it allows you to collect and analyze error data from your users in real time.

How can I handle CORS errors with window.onerror?

CORS errors, or Cross-Origin Resource Sharing errors, occur when a web application tries to access resources from a different domain. These errors are security-related and do not provide detailed information to the window.onerror event handler due to security reasons. However, you can handle these errors by adding error handlers to the specific network requests that might cause CORS errors.

What are some best practices for using window.onerror?

When using window.onerror, it’s important to handle errors in a way that doesn’t disrupt the user experience. You should avoid showing technical error messages to the user. Instead, consider displaying a friendly error message or silently logging the error. Also, be mindful of the performance impact of your error handling code. For example, if you’re sending error reports to a server, make sure to do it asynchronously to avoid blocking the main thread.

Ben VinegarBen Vinegar
View Author

Ben is VP Engineering at Sentry, where he oversees an organization of 40 engineers. Together, the team has upgraded the front-end, added support for mobile platforms, and scaled Sentry's hosted product to support over 100,000 active users and more than 9,000 paying customers. He is also the author of Third-Party JavaScript and a speaker on browser-side technologies. Previously, Ben led teams at Shape Security, Disqus, and FreshBooks.

error monitoringjoelfsentrysponsored
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week