Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catching exceptions in setInterval

Quick question, if I do this:

setInterval(function() {
    try {
        riskyFunc();
    } catch(e){
        console.log(e);
    }
}, 1000);

In my head I am thinking that if anything goes wrong in riskyFunc(), it will be caught. Is this true? There are also some async calls I know of for sure inside riskyFunc().

like image 393
nemo Avatar asked Jul 01 '14 16:07

nemo


2 Answers

Yes, it will be caught: but only when the callback is executed. That is, if riskyFunc throws an exception, it won't be caught in your example until the callback executes in one second.

You've probably heard before that you have to be careful with exceptions when using asynchronous methods, and the usual mistake people make is this:

try {
    setInterval(function() {
        riskyFunc();
    }, 1000);
} catch(e) {
    console.error(e);
}

They are confused when riskyFunc throws an exception and it isn't caught. It isn't caught because the exception doesn't happen when you call setInterval; it happens when setInterval invokes the anonymous function sometime in the future, which is outside of the context of the original try/catch block. You are doing it the correct way: by doing the exception handling inside the callback.

If riskyFunc in turn invokes asynchronous calls, those too have to handle exceptions in this manner. For example:

function riskyFunc() {
    // do some stuff
    asyncFn(function(){
        throw new Error('argh');
    }
}

That exception will not get caught in the try/catch block inside your setInterval call. You'll have to keep applying the pattern on down:

function riskyFunc() {
    // do some stuff
    asyncFn(function() {
        try {
            // work that may throw exception
        } catch(e) {
            console.error(e);
        }
    }
}

If you want the exception to "propagate up", you'll have to use promises, or some other way to indicate success/failure. Here's a common method, by using a "done" callback that is capable of reporting an error:

function riskyFunc(done) {
    // do some stuff
    asyncFn(function() {
        try {
            // do some more risky work
            done(null, 'all done!');
        } catch(e) {
            done(e);
        }
    }
}

Then you can call that in your setTimeout and take into account possible asynchronous failures:

setTimeout(function() {
    try {
        riskyFunc(function(err, msg) {
            // this will cover any asynchronous errors generated by
            // riskyFunc
            if(err) return console.error(err);
            console.log(msg);
        });
    } catch(e) {
        // riskyFunc threw an exception (not something it
        // invoked asynchronously)
        console.error(e);
    }
}
like image 99
Ethan Brown Avatar answered Sep 26 '22 03:09

Ethan Brown


setInterval already puts the block in an asynchronous block. And exceptions can't be caught in out-of-sync. The right pattern is to use a Promise object, and call a fail() operation if something goes wrong. For this example, I'm using jQuery's Deferred object, but any promise library has similar usage:

var promise = $.Deferred();

setInterval(function () {
    try{
       riskyFunc();
    } catch (e) {
       promise.reject(e);
    }
    promise.resolve(/* some data */);
}, 1000);

promise
    .done(function (data) { /* Handled resolve data */ })
    .fail(function (error) { /* Handle error */ });

Note that since you're using setInterval instead of setTimeout that this will be called every second unless you clear the timeout, so if you need to call the function multiple times in parallel, you might want an array of promises.

like image 26
Gingi Avatar answered Sep 27 '22 03:09

Gingi