March 11, 2019

Don't Use Try/Catch in JavaScript II

If you're using try/catch in JavaScript you're doing something WRONG. Here's why.

In part one, I discussed why try/catch is useless for handling networking errors.

In part two, I will outline more areas where try/catch is not useful.

Around disk access

You don't have permission to access the local disk in the browser. There is the File API, but this does not allow local disk access. It allows you access to a copy of files the user has selected with the file selection dialogue. Hence, try/catch is not useful for handling disk errors or access exceptions, because there are none.

If, on the other hand, you are writing server-side JavaScript, it's likely you will be using a promise-based scheme or a callback scheme to access files. A try/catch will not sufficiently address this for similar reasons described in part 1.

Around devices/drivers

Not super common with JavaScript, but you'll probably be using promises or callbacks and Node.js.

Around user-input

Using try/catch to verify user-input is just sloppy work. Try/catch is typically used to validate numbers and dates incorrectly. See the following examples for more info:

Validate a Number:

var input = "3 days";
//INCORRECT WAY #1
try{
    var test = input - 4;
}
catch{
    //no error happens - test is the NaN value.
}

//INCORRECT WAY #2
if(parseInt(input)){
    //"0 days" returns false, "3 days" returns true.
    //in older browsers javaScript parseInt("013") would return "11"
    //because a leading 0 makes javaScript assume the number is octal/base-8
}

//CORRECT WAY
if(Number.isNaN(Number(input))){     //Number.isNaN is not the same as isNaN
    //not a number
}

Validate a Date:

//INCORRECT WAY
var input = "23-03-2019";   //because I'm Australian, format is DD-MM-YYYY
try{
    var test = new Date(input);
}
catch{
    //no exception, test is the "Invalid Date" object.
}

//CORRECT WAY
var r = /^(\d\d?)-(\d\d?)-(\d\d\d\d)$/;
if(r.test(input){
    var parts = input.split("-");    //could also use Regex's capturing groups
    var test = new Date(Number(parts[2]), Number(parts[1])-1, Number(parts[0]));
    if(!Number.isNaN(Number(test.getTime()))
        && test.getMonth() + 1 === Number(parts[1])
        && test.getDate() === Number(parts[2])
        && test.getYear() === Number(parts[0])
        ){
        //valid date.
    }
}

As exception handlers

Try/catch is an overhead cost that will slow down responsiveness of your site. An exception handler implementation using try/catch is possible, but as already described, it will not capture a wide range of async scenarios. It is certainly not the silver bullet it is in other languages. There are exception handlers out there for Node.js (listen on the uncaughtException event) but there isn't a strong case for it in client-side JavaScript.

Capturing exceptions for control-flow reasons

If you are working in non-async code/APIs and you want to raise errors so that the error can be handled in a more convenient area of code, then you CAN do this in JavaScript, but you probably don't need try/catch for this, and try/catch will slow down your code.

For Node.js servers, there is a place for informational exceptions, but traditionally this would never be in the try/catch form, instead you would chose one of the following patterns:

  • returning an Error
  • calling the callback and passing an Error
  • emitting an Error event (eventful pattern only)

This is also how I would suggest you handle errors in client-side JavaScript if you wanted to go for a control-flow pattern.

Since Node 7.4 with introduction of the await and async keywords, try/catch has become a viable option for handling errors in that technology stack. This is the only concession I will make to my opening claim: If you're using try/catch in JavaScript, you're doing something wrong.