The try/catch block is a unique construct, both in how it works and what it is capable of. Fundamentally, it is able to isolate one or more statements to capture and suppress any runtime errors that may be encountered as a result of execution. It is such a powerful construct that in a perfect world you would want to wrap everything in a try/catch block to provide simple and effective error trapping. However, due to concerns in performance critical situations, employing the construct is often frowned upon. But what if I told you there was a means of emulating the try/catch block without the concern for performance? This article will be exploring just such a method.

Performance Implications of the Try/Catch Block

What makes this construct unique is in the manner in which the catch block augments the scope chain. Rather than creating a new execution context and pushing it to the top of the execution stack, the catch block will actually create a new variable object and place it ahead of the activation object in the scope chain of the current execution context. This creates what is known as a dynamic scope, similar to the effect of the with statement, which lends to its bad reputation as well. As a result, the error object passed to the catch block does not exist outside of it, even within the same scope. It is created at the start of the catch clause and destroyed at the end of it. This type of manipulation of the scope chain is the primary contributor to the performance hit.

At this point you may be thinking that as long as an error is not raised than performance should not be affected, a fair assumption, but you’d be wrong. Some JavaScript engines, such as V8 (Chrome) do not optimize functions that make use of a try/catch block as the optimizing compiler will skip it when encountered. No matter what context you use a try/catch block in, there will always be an inherent performance hit, quite possibly a substantial one.

These limitations are well documented, for instance, look at the following test cases: http://jsperf.com/try-catch-block-performance-comparison and http://jsperf.com/try-catch-block-loop-performance-comparison. The former confirms that not only is there up to a 90% loss in performance when no error even occurs, but the declination is significantly greater when an error is raised and control enters the catch block. The latter test case proves that the loss is compounded in loops, where most performance intensive operations typically occur.

The Solution

To find a suitable alternative, we require a reliable means of error notification. For this, there is really only one viable source in the browser we can turn to for error trapping, that being window.onerror. The question is, can we leverage the event as a means of closely mimicking the functionality of a try/catch block? The answer is yes… for the most part. The event is capable of detecting runtime errors including any errors that you explicitly throw yourself. We can also imitate the error suppression of a catch block by returning false from window.onerror, while returning true allows the error to propagate to the browser.

With these advantages, creating a custom alternative that is capable of simulating the primary functionality of a try/catch block becomes rather easy. In its simplest form, the solution involves temporarily caching a “catch” function before invoking a “try” function, then quickly nullifying the “catch” function after the “try” function has completed execution. This allows us to reliably resolve the source of an error when one is encountered. We then invoke the associated “catch” handler passing an error object constructed from the message, file name, and line number provided to the window.onerror event handler:

(function(win){
    'use strict';

    var callback = null, handler = win.onerror;

    win.tryCatch = function(tryFn, catchFn){
        callback = catchFn;
        tryFn();
        callback = null;
    };

    win.onerror = function(msg, file, line){
        var error = new Error(), suppress;
        error.message = msg;
        error.fileName = file;
        error.lineNumber = line;
        if(callback){
            suppress = callback(error);
            callback = null;
            return suppress === false ? false : true;
        }
        return handler ? handler.apply(win, arguments) : false;
    };

})(this);

The function, appropriately named tryCatch, allows us to wrap error-prone or sensitive code in a similar fashion as a try/catch block. The syntax is similar as well, the exception being the use of functions instead of block statements, such as the following:

tryCatch(function(){
    // try something   
}, function(error){
    // handle error
});

The error object passed to the callback has the added benefit of retaining the line number and file name for the location of the error, something that is widely inconsistent amongst browsers when employing a try/catch block.

One of the advantages to this approach is the countless ways we can augment and expand the method to suit various needs. For example, we could add greater debugging capabilities by storing additional information about the source of an error such as the test function itself or any variables the function makes use of. This could help expand error logging to better understand where and why an error took place.

Performance Comparison

When we measure the performance of this custom solution against a try/catch block, we get some interesting results. Based on some preliminary tests, I found performance comparisons between the browsers fairly inconsistent. Firefox, IE, and Opera all showed improved performance using the tryCatch function as opposed to a try/catch block, while the results were opposite for Chrome and Safari. However, when we avoid needlessly creating anonymous functions for every invocation of the tryCatch function and instead use predefined functions, performance actually improves quite a bit in all browsers. Check out the results and try it yourself at: http://jsperf.com/native-try-catch-vs-custom-try-catch/7. The real advantages come inside loops, in this case performance increased dramatically in most browsers: http://jsperf.com/native-try-catch-vs-custom-trycatch-loop.

Drawbacks

Despite some of the advantages to this approach, it is not without its caveats. Most of these disadvantages are closely related to the window.onerror event and can be avoided if used properly in the right circumstances.

One such disadvantage is when the “catch” handler is invoked in the case of an error and another error occurs within the handler, then both errors will propagate to the browser. This happens because control still hasn’t left the window.onerror event handler from the initial error, raising another error will stop execution and resort to the default behaviour.

There is also a lack of information available beyond the message, file name, and line number of the error provided to the window.onerror event handler. We don’t get the error type contained in the name property of an error object, although Firefox does prepend this to the message itself. We also don’t get a stack trace, simple reason being that a stack trace is related to an execution context, whereas window.onerror is invoked in the global execution context. These omissions do limit our ability to more effectively debug an error.

Another drawback inherent to window.onerror is that once an error is encountered, the browser will stop execution following the invocation of the event handler. Any code following the tryCatch call will be skipped as the interpreter will instead proceed to the next script block (<script></script>). This is unavoidable, only a catch block is capable of truly suppressing an error without halting execution. Of course, if no further execution is required than this drawback is irrelevant.

Perhaps the greatest disadvantage to this solution are the question marks surrounding window.onerror. For example, it’s not even clear what errors trigger the event and which don’t. The official Mozilla documentation supports this:

Note that some/many error events do not trigger window.onerror, you have to listen for them specifically.

Unfortunately, Mozilla doesn’t get any more specific in terms of which errors are actually caught by window.onerror. According to the Internet Explorer documentation, the following errors trigger the event:

  • Run-time script error, such as an invalid object reference or security violation.
  • Error while downloading an object, such as an image.
  • Windows Internet Explorer 9. An error occurs while fetching media data.

This seems to be consistent with most browsers, but again, not confirmed. While it is easy to test for which errors currently trigger the event, it is the lack of a clear definition for what should trigger it that makes for the problem. Instead, each browser is left to their own devices to decide what should and should not constitute the triggering of window.onerror. With that type of implementation, how can we trust such an event in every scenario moving forward?

Conclusion

When I initially began experimenting with this method, I was looking for a means of replacing a try/catch block for performance benefits. However, in the end, it started to dawn on me that perhaps the greatest advantage to this solution is in the numerous opportunities for customization. The ability to tailor the tryCatch function for different use cases and meet various requirements you otherwise couldn’t achieve with a try/catch block could prove to be a more valuable means of error trapping and debugging.

You could look at it as a suitable median. On one hand, you have a method that mimics a try/catch block but has greater opportunities in terms of functionality while also more beneficial in performance critical situations in some circumstances. On the other hand, it can narrow the scope of a window.onerror event to specific fragments of code for more effective debugging. Kind of a best of both worlds mashup.

Now let me be clear, I am absolutely NOT advocating that you replace all your try/catch blocks using this method. This is still very much in the experimental stage, relying on this method in any type of production environment would be premature. There are just too many questions that need answering.

I would, however, encourage you to fork the code on Github. Tweak it, improve it, test it out in scenarios I haven’t even thought of, and maybe we can learn more.

  • http://paulirish.com Paul Irish

    onerror now captures the stack and it’s implemented in Chrome:
    https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror

    I dont know of any situations that window.onerror (or addEventListener(‘error’)) would not catch that try/catch would. 
    Maybe something else in the spec around this? http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#dfnReturnLink-1

  • Hannes

    Hey,

    Interesting idea and decent performance gains.

    With regards to the halting of execution, what do you think the rationality of adding a third “finally” parameter?

    win.tryCatch = function (tryFn, catchFn, finallyFn)
    {
            callback = catchFn;
            tryFn();
            callback = null;
           
            if (finallyFn)
                    finallyFn();
    };

    • Ryan

      That was an idea I was experimenting with, the trick was trying to get the “finally” callback to execute after the “catch” callback, which is proper behavior. To do so, I was using setTimeout(finallyFn) to remove the call from the event loop and hopefully have it invoke after execution has halted from the error. Results were inconsistent, so I decided to remove it from the project. However, it is something I have thought to revisit in the future.

  • http://www.dave78.com/ Dave Clayton

    I find the benchmarks you link to somewhat misleading, as they are not doing any useful work — they’re just doing the try/catch. When I benchmarked our code with and without try/catches both around every input event and inside the most frequently executed parts of our application, I found the maximum difference to be around 5% because I wasn’t just measuring the try/catch itself.

    Please, readers, if you see this article, don’t go away thinking “I must convert all my try/catches to use this method or my code will be really slow!”. Measure first, then optimize *where you get the biggest gains*. Chances are it won’t be rewriting your error handling code.

    • http://ryanmorr.com/ Ryan Morr

      The benchmarks are merely a simple example to show the impact on performance, because there is one. Attempting to replicate any real-world scenario would be impractical.

      Secondly, I never advocated converting try/catch blocks to my solution, I clearly state that in the second to last paragraph of the conclusion. It’s just an experiment.