Even suggesting error handling can get you told off in some JavaScript forums. The reality is, therefore, that when JavaScript projects fail, they often fail silently, without anyone knowing. The user gets a substandard experience, and the developer lives in ignorance of the defect.
So here is my advice for why and when to use try-catch error handling in your JavaScript projects.
What error handling?
When I say error handling, I’m not talking about doing code checks and adding err methods on callbacks; I assume you do that already. I’m talking about using try-catch to handle it when your code throws an exception.
In its simplest form, try-catch works like this:
function foo(){
var foo1 = bar();
alert("Done");
}
Becomes:
function foo(){
try {
var foo1 = bar();
alert("Done");
}
catch(e){
alert("Your error was:"+e+", have a nice day.");
}
}
JavaScript has had support for the try-catch syntax since version 1.5 (or ECMAScript Edition 3, for the purists) circa 1999, so it’s nothing new. What is stunning is the number of JavaScript developers who don’t even know it’s there.
Why error handling?
The first and best reason for error handling is that you are not perfect.
It comes as a shock, I know, but someone had to tell you sooner or later.
Even well-formatted code in a highly fault-tolerant language like JavaScript will throw an error from time to time. You can use JSLint to spot any syntax errors or poor form, but it won’t and can’t catch all logical errors. Mistakes get made, calls get made out of scope or on the wrong objects, or a host object may throw an exception directly.
Rule of thumb? If a user expects to be told when your code doesn’t work, there should be error handling. If you want to know if your code is failing, there should be error handling.
Trust me on this: Users would rather see a message “An error has occurred. Unable to save your work at this time” than to hit save and just assume it worked.
Catching errors is your backup plan, the chance to fail gracefully, report a defect, or just let the user know, rather than crash and burn silently with no record.
When to try-catch
One reason why people don’t like error handling is that they think it has to go everywhere. This is wrong. You only ever have to wrap code in a try-catch when you intend to respond to an error. Small functions don’t need error handling, because their errors will bubble up to the calling code. If you’re not going to do something like display a custom error message, then you don’t need to use error handling. No matter how many functions deep you go, a single try-catch at the top will pick up any error thrown.
The ideal location for a try-catch is at the point where code execution begins. This means putting it around the main code block of a script, where objects are created and event listeners assigned. It also means putting it inside the event listeners themselves. This way, if important code is running, it’s doing so inside a try-catch.
Asynchronous calls like a JSON-get are not as different as you might think. True, a try-catch on the async call itself won’t catch anything in the callback, because it’s not executed in that try-catch context. However, that doesn’t mean you can’t put a try-catch inside the callback. Remember, a callback is another point where execution is initiated.
Keep in mind that error handling is not the same as recovering. You can still have fail-fast code with error handling by catching the error, logging a debug, and throwing the same error on again. It just means you know when it has failed fast. What you do with the knowledge is up to you.
Finally, not every project needs error handling; some functionality sits too far outside the critical path of usability to warrant the overhead. But if your code is important enough, then make sure your coverage is complete.
What’s the cost?
The biggest argument against error handling in JavaScript is that it impacts performance, and it’s a valid concern. Try-catch, like any other code, has some performance impact when used, and can have a great deal of impact when it’s not used well.
The performance impact of try-catch is much more pronounced when working in Node.JS than in most (if not all) web client contexts. The V8 engine that Node.JS uses is much faster than its web client cousins, because it’s highly optimised for speed, and try-catch affects that optimisation.
You’ll see the biggest performance hits when allocating lots of variables inside a try-catch block, and the impact will be magnified by any intense looping.
However, this performance hit is easily avoided. It takes only minor changes to the way try-catch is applied to maintain your code efficiency. For performance-critical loops, all we need to do is place the try-catch outside the function scope of the loop, and JavaScript makes it easy to wrap the loop code in its own function (closure). For example:
function foo() {
try {
for (var i = 0; i < 1000; i++) {
...
// Doing lots of variable assignments here!
...
}
}
catch (e) {
// Handle your errors
...
}
}
Becomes:
function foo(){
function bigloop() {
for (var i = 0; i < 1000; i++) {
...
// Doing lots of variable assignments here!
...
}
}
try {
bigloop();
}
catch (e) {
// Handle your errors
...
}
}
This simple practice has very little impact on the code itself. You don’t have to change scope or pass lots of variables for instance, and it nicely resolves the performance impact of using try-catch to a negligible level.
You can see a practical example of this approach, and the performance comparisons on various script engines here. Just scroll down to compare your web client with other browsers, and you’ll notice that only some JavaScript engines are impacted to begin with.
The wrap-up
The most important thing to stress here is that there is a case for error handling in JavaScript. It is needed, it is useful, and there are ways around the concerns such as overuse and the rare performance costs.
Don’t let other developers convince you that try-catch is evil in JavaScript. Even in the Node.JS world, it has its place. In a perfect world, your error handling will never get used, but who lives in a perfect world?