Matt Snider JavaScript Resource

Understanding JavaScript and Frameworks

Saturday, April 5, 2008

Catching All JavaScript Errors in All Browsers

I recently wrote an article about the JavaScript “onerror” event. The “onerror” event is really powerful in that you can prevent all JavaScript errors from ever being shown to the user, as well giving you the ability to capture each error and send it to the server for logging. The major downside is that only the major browsers (IE and FireFox) support ‘onerror’, because of this you miss all the goodness in Safari, Opera, and other smaller browsers. I spent some time over the last week and devised a solution that will allow you to catch all errors in all browsers. However, it is rather onerous, so I present it here, but hope that some of you can suggest ways to help improve it.

The sure-fire way to capture all errors in all browsers, is to put a try/catch statement inside each Function. Obviously, doing so would be ridiculous, even though you are trying to solve a huge problem: preventing and capturing JavaScript errors. However, you can instead create a proxy Function that handles the try/catch wrap for you. Instead of calling a Function/Method directly, you will use the proxy Function throughout your code. Inside the proxy, we put a try/catch statement to capture exceptions. The catch statement will send an AJAX message to log the error server-side, and return NULL. Otherwise the proxy Function will return the same value that would have normally been returned by executing the passed method. While, still onerous, this is probably a do-able solution:

Example 1: The Call_User_Method_Array Function

/** * Calls the method, scoped to the passed object, with the set of parameters. * * @method call_user_method_array * @param method_name {String} Required. The method name on ‘obj’. * @param obj {Object} Required. The object owning the method, that the method will also be scoped as. * @param paramarr {Array} Required. The collection of parameters. * @static */ var call_user_method_array = function(method_name, obj, paramarr) { try { return obj[method_name].apply(obj, paramarr || []); } catch (e) { window.onerror(e.message, window.location, ‘-1′); // use -1 for exceptions return null; } };

I modeled the method after a similar method used by PHP. The method should be placed early in global namespace, and I would recommend abbreviating the name to no more than 3 characters as it will be called many, many times. Below are a few examples of how to use this method:

Example 2: How to Use Call_User_Method_Array

// calling a method on window var testFunc = function() {/* do something */ }; call_user_method_array(’testFunc’, window); // calling a method on a string var testString = ‘This is a string’; var index = call_user_method_array(’indexOf’, testString, [’string’]); // calling a nested method var bool = call_user_method_array(’isIE’, Core.Client);
posted by Matt Snider at 3:26 pm  

6 Comments »

  1. Smart work, as always. When using technique, however, keep in mind that there is a performance hit to the use of try/catch.

    Comment by Paul Irish — April 6, 2008 @ 10:54 am

  2. Paul,

    Great work, I have experienced the performance that you describe, but never quantified it. Thanks for the effort.

    Comment by Matt Snider — April 6, 2008 @ 11:11 am

  3. We considered doing something similar which involved hacking yui’s addlistener, but decided against it for the following reasons:

    1.) we have a decent qa on staff (finally!)
    2.) most errors will be caught by IE/FF
    3.) it felt too so darn dirty
    4.) performance

    …in that order.

    Comment by Ryan Dunphey — April 6, 2008 @ 10:52 pm

  4. I don’t think there’s a clean way to do proper error checking in JavaScript. I’ve always found the try/catch syntax to be awkward for most cases. However, to create a method that tracks all errors, using try/catch is unavoidable. I think the method you provided is a bit heavy handed. I think it would be better to just have a central method with one try/catch that runs all the other functions when an event is triggered. If it errors you don’t have as much insight as to what happened, but you can use your event object to find out what the source object was, what event triggered it, the browser the user was working from, etc…Then you try to narrow down what went wrong. You don’t have to worry about the performance hit as you’re only calling the one try/catch.

    Comment by MillsJROSS — April 7, 2008 @ 5:37 am

  5. I have a couple of ideas:

    1) put the object first, and method name second in call_user_method_array(). That way they are in the same order as a function call.

    2) if not, you could make the 2nd argument default to window if it is not supplied, to keep the calling code simpler.

    3) rather than pass an array as the third param, you could get the values from the arguments array. It would look something like this:

    var call_user_method_array = function(method_name, obj /*, paramarr*/) {
    var args = Array.prototype.slice.call(arguments, 2);
    try {
    return (obj||window)[method_name].apply((obj||window), args);
    }
    catch (e) {
    window.onerror(e.message, window.location, ‘-1′); // use -1 for exceptions
    return null;
    }
    }

    Comment by Lowell — July 29, 2008 @ 10:42 pm

  6. Good points… thanks for you thoughts, Lowell.

    Comment by Matt Snider — July 30, 2008 @ 9:32 am

RSS feed for comments on this post. TrackBack URI

Leave a comment

Powered by WordPress