34

Would it be faster to just put code inside a try-catch block instead of performing various error checks?

For example..

function getProjectTask(projectTaskId) {
    if (YAHOO.lang.isUndefined(projectTaskId) || YAHOO.lang.isNull(projectTaskId) && !YAHOO.lang.isNumber(projectTaskId)) {
        return null;
    }

    var projectPhaseId, projectPhaseIndex, projectTaskIndex, projectPhases, projectPhase, projectTask;

    if (!YAHOO.lang.hasOwnProperty(projectTaskPhaseMap, projectTaskId)) {
        return null;
    }

    projectPhaseId = projectTaskPhaseMap[projectTaskId];

    if (YAHOO.lang.isUndefined(projectPhaseId) || YAHOO.lang.isNull(projectPhaseId) || !YAHOO.lang.hasOwnProperty(scheduleData.ProjectPhasesMap, projectPhaseId)) {
        return null;
    }

    projectPhaseIndex = scheduleData.ProjectPhasesMap[projectPhaseId];
    if (YAHOO.lang.isUndefined(projectPhaseIndex) || YAHOO.lang.isNull(projectPhaseIndex) || !YAHOO.lang.hasOwnProperty(scheduleData.ProjectPhases[projectPhaseIndex])) {
        return null;
    }
    projectPhase = scheduleData.ProjectPhases[projectPhaseIndex];

    if (!YAHOO.lang.hasOwnProperty(projectPhase.ProjectTasksMap, projectTaskId)) {
        return null;
    }

    projectTaskIndex = projectPhase.ProjectTasksMap[projectTaskId];

    if (YAHOO.lang.isUndefined(projectTaskIndex) || YAHOO.lang.isNull(projectTaskIndex)) {
        return null;
    }

    projectTask = scheduleData.ProjectTasks[projectTaskIndex];
}

VS

function getProjectTask(projectTaskId) {
    try {
        projectPhaseId = projectTaskPhaseMap[projectTaskId];
        projectPhaseIndex = scheduleData.ProjectPhasesMap[projectPhaseId];
        projectPhase = scheduleData.ProjectPhases[projectPhaseIndex];
        projectTaskIndex = projectPhase.ProjectTasksMap[projectTaskId];
        projectTask = scheduleData.ProjectTasks[projectTaskIndex];

    }
    catch (e) {
        return null;
    }
}

I hope my question makes sense. I would be happy to clarify. Thank you!

gblazex
  • 49,155
  • 12
  • 98
  • 91
Abe
  • 6,386
  • 12
  • 46
  • 75
  • even `function isNull(c)` is useless, see my answer... :) – gblazex Jul 10 '10 at 00:48
  • I'm not familiar with YUI either, but checking for null || undefined can be done easily with javascript – gblazex Jul 10 '10 at 01:50
  • Sorry, I reformatted the code section to have less scrolling. You guys are right. I tried to make use of the YUI library code when a simple built-in javascript statement would suffice. =P – Abe Jul 10 '10 at 23:42

8 Answers8

49

"Programs must be written for people to read, and only incidentally for machines to execute."

Abelson & Sussman, SICP, preface to the first edition

Always aim for readable code. The key thing to remember is:

Avoid try-catch in performance-critical functions, and loops

Anywhere else they won't do much harm. Use them wisely, use them when they make sense.

But as I see you clearly misuse some functions for error checking. You can test for the desired objects and properties of objects right before you use them instead of complex checking. And:

if (YAHOO.lang.isUndefined(projectPhaseId) || YAHOO.lang.isNull(projectPhaseId))

can be written as

if (projectPhaseId != null)

for example... So the example above can be fairly readable even without try catches. You seem to misuse YUI a bit.

I would bet this works as expected:

function getProjectTask(projectTaskId) {

   var projectPhaseId    = projectTaskPhaseMap[projectTaskId],
       projectPhaseIndex = scheduleData.ProjectPhasesMap[projectPhaseId],
       projectPhase      = scheduleData.ProjectPhases[projectPhaseIndex];

  if (projectPhase == null) return null; // projectPhase would break the chain

  var projectTaskIndex  = projectPhase.ProjectTasksMap[projectTaskId],
      projectTask       = scheduleData.ProjectTasks[projectTaskIndex];

   return projectTask || null; // end of the dependency chain

}

How cool is that? :)

gblazex
  • 49,155
  • 12
  • 98
  • 91
  • 10
    Well put. I'd just like to add that unless you actually have a performance problem, it's best to make your code readable. When you actually do have a performance problem, first measure where the problem is, only then optimize. Otherwise you may spend much time optimizing the wrong things. – Jani Hartikainen Jul 10 '10 at 00:36
  • This is an excellent answer. There are times and cases for using try-catch, despite the open source community's continual battle cry against exception handling code. It doesn't matter how fast your code is, if nobody can read, understand or maintain it except you. – whoblitz Aug 13 '13 at 03:02
  • I'd say performance-wise you can go crazy with try/catch. Don't know what the situation was back in '13 but right now jsperf shows almost no performance hit on firefox and chrome(won't bother and actively refuse to test in IE). I borrowed this test and added my own snippet to test performance without any type of error catching: http://jsperf.com/native-try-catch-vs-custom-trycatch-loop/6 – jpeltoniemi Feb 24 '14 at 03:49
28

Why not have a fact basis for the argument? The following code demonstrates the performance impact:

var Speedy = function() {
    this.init();
};
Speedy.prototype = {
    init: function() {
        var i, t1;
        this.sumWith = 0;
        this.sumWithout = 0;
        this.countWith = 0;
        this.countWithout = 0;
        for (i = 0; i < 5; i++) {
            t1 = this.getTime();
            console.log("Using Try/Catch, Trial #" + (i + 1) );
                        console.log("started " + t1 );
            this.goTry(t1);
            this.countWith++;
        }
        for (i = 0; i < 5; i++) {
            t1 = this.getTime();
            console.log("W/out Try/Catch, Trial #" + (i + 1) );
            console.log("started  :" + t1 );
            this.goAlone(t1);
            this.countWithout++;
        }
        for (i = 5; i < 10; i++) {
            t1 = this.getTime();
            console.log("Using Try/Catch, Trial #" + (i + 1) );
            console.log("started  :" + t1);
            this.goTry(t1);
            this.countWith++;
        }
        for (i = 5; i < 10; i++) {
            t1 = this.getTime();
            console.log("W/out Try/Catch, Trial #" + (i + 1) );
            console.log("started  :" + t1);
            this.goAlone(t1);
            this.countWithout++;
        }
        console.log("---------------------------------------");
        console.log("Average time (ms) USING Try/Catch: " + this.sumWith / this.countWith + " ms");
        console.log("Average time (ms) W/OUT Try/Catch: " + this.sumWithout / this.countWithout + " ms");
        console.log("---------------------------------------");
    },

    getTime: function() {
        return new Date();
    },

    done: function(t1, wasTry) {
        var t2 = this.getTime();
        var td = t2 - t1;
        console.log("ended.....: " + t2);
        console.log("diff......: " + td);
        if (wasTry) {
            this.sumWith += td;
        }
        else {
            this.sumWithout += td;
        }
    },

    goTry: function(t1) {
        try {
            var counter = 0;
            for (var i = 0; i < 999999; i++) {
                counter++;
            }
            this.done(t1, true);
        }
        catch (err) {
            console.error(err);
        }
    },

    goAlone: function(t1) {
        var counter = 0;
        for (var i = 0; i < 999999; i++) {
            counter++;
        }
        this.done(t1, false);
    }
};

var s = new Speedy();

This JSFiddle will show you the output in firebug lite's console: http://jsfiddle.net/Mct5N/

blong
  • 2,815
  • 8
  • 44
  • 110
Jon J
  • 491
  • 5
  • 8
  • 6
    The implementation above no longer works (jsFiddle doesn't like `document.writeln` ). Here's an updated version: http://jsfiddle.net/Mct5N/ – blong Apr 16 '13 at 19:32
  • 1
    This meant well but seems to not be testing the thing in question. – King Friday Apr 20 '15 at 15:42
4

Placing dogma aside and not being satisfied with the answers here at the moment...

If your code rarely throws exceptions, placing a try-catch around the offender performs well because there is no additional overhead in catching the exception or preventing it.

If the code commonly throws exceptions based on unpredictable data or some scenario similar to that, placing a guard method increases performance considerably, up to 20 times if exceptions occur often.

If I were to advise an approach, use simple guard operators when possible if there isn't deep nesting. In cases of deeper nesting, use a guard method that can traverse through as needed.

Here's some testing of my own that I based this off of.

http://jsfiddle.net/92cp97pc/6/

Scenarios are comparing the following but in loops:

var a;

// scenario 1 (always throws/catches)
try { a.b.c.d; }
catch(ex) { }

// scenario 2 (about 20 times faster than scenario 1)
guard(a, 'b', 'c', 'd');

// now no exceptions will occur
a = { b: { c: { d: true } } };

// scenario 3 (too fast to measure)
try { a.b.c.d; }
catch(ex) { }

// scenario 4 (.04 times slower than scenario 3)
guard(a, 'b', 'c', 'd');
King Friday
  • 25,132
  • 12
  • 90
  • 84
3

Sure, it makes for more compact code, but it reduces your debug ability and makes adding graceful error-recovery, or useful error messages much, much, harder.

Brock Adams
  • 90,639
  • 22
  • 233
  • 295
1

Depends on the situation. As galambalazs mentions readability is important. Consider:

function getCustomer (id) {
  if (typeof data!='undefined' && data.stores && data.stores.customers 
      && typeof data.stores.customers.getById=='function') {
    return data.stores.customers.getById(id);
  } else {
    return null;
  }
}

compared to:

function getCustomer (id) {
  try {return data.stores.customers.getById(id);} catch (e) { return null; }
}

I'd say the second is much more readable. You tend to get data back like this from things like google's apis or twitter's feeds (usually not with deeply nested methods though, that's just here for demonstration).

Of course, performance is also important, but these days javascript engines are fast enough that nobody's likely to notice a difference, unless you're going to call getCustomer every ten milliseconds or something.

Dagg Nabbit
  • 75,346
  • 19
  • 113
  • 141
0

Keep in mind that this varies based on browsers as well but overall I have not read anything about significant performance penalties for using a try/catch block. But it's not exactly a good practice to get into using them because you are not able to tell why a problem failed.

Here is an interesting slide show of some javascript performance considerations. On slide 76 they cover try/catch blocks and the performance impact. http://www.slideshare.net/madrobby/extreme-javascript-performance

spinon
  • 10,760
  • 5
  • 41
  • 59
0

Performance wise try-catch is 20-50% slower than if checks (https://jsperf.com/throw-catch-vs-if-check/1).

So, For rare usage, doesn't make much difference. For heavy usage, it might make some difference.

However, I feel it's bad practice to use try-catch, if it can be done by if checks except if it greatly reduces readability.

Anil Tallam
  • 331
  • 4
  • 10
0

Performance Comparison for Node JS 18.x (adding this so actual ms difference can be compared)

Updating this answer, because could not find performance metrics

Our Case:

We came across this when a developer had implemented caching, and threw a error when the cache returned null Just to handle the error in the same function, cache it and return a response.

Have seen this pattern in multiple places, where errors/nulls are expected to be thrown and handled in the scope of the same function

Code to test:

function conditional() {
  if (true) {
    return 'data';
  }
}

function tryCatch() {
  try {
    throw new Error('Error');
  } catch {
    return 'data';
  }
}

console.time('tryCatch');
for (var i = 0; i < 10000; i++) {
  tryCatch();
}
console.timeEnd('tryCatch');

console.time('conditional');
for (var i = 0; i < 10000; i++) {
  conditional();
}
console.timeEnd('conditional');

Results:

tryCatch: 19.318ms. -> average 18ms

conditional: 0.218ms -> average 0.2ms

This means it 18/0.2 = 90 times faster OR 9000% faster

Well thats some performance optimisation to strive for

Link to example:

https://stackblitz.com/edit/node-xph32b?file=index.js

Conclusion:

Throwing Errors + Try-Catch does has a performance penalty attached to it, if the error is expected and to be handled in the code its better to use conditional statements instead of throwing errors

Sunny
  • 932
  • 8
  • 22