Skip to content

After setTimeout, this loses context

I have this self made function called succeeder which is supposed to try to run a function called func and if it fails to try and run it again after an interval.

This works great the first time and when func is called for the second time after setTimeout it fails and this seems out of context.

Can you think of anything that is not right in this snippet?

succeeder({
    func : function () {
        !this.dbOpen && this.init();
        return true;
    },
    context : this,
    interval : 2000,
    success : function () { return true;}
});

function succeeder(opts) {
    function run() {        
        try {           
            //_.delay(function(){opts.func();}.bind(opts.context), 2000);
            setTimeout(function(){ return _.bind(opts.func, opts.context)(); }, 2000);
        } catch (e) {
            run(this.opts, this.interval);
        }       
    }
    run();      
};

Answer

Your code doesn’t do anything like the description. Also the description isn’t clear: what does “if it [func] fails” mean? Does func throw an exception? Return false? Or what?

There is no code to check the return value from func, yet the example version of func returns true. What is the purpose of the true return value?

You have a try/catch block, which made me suspect you are talking about the func call throwing an exception. But this try/catch won’t catch any exception that func throws!

That’s because the try/catch is wrapping the setTimeout call. But func is not called from inside that try/catch block. It’s called later, when the timeout fires, and the try/catch is no longer in effect at that time.

What is the opts.success function? It is never used.

Also, the code always takes a two-second delay before the first attempt to call func. Do you want that, or do you want the first call to be immediate and only take the delay if the call fails and you retry?

Here is a working example that does something like what you’re talking about. I made the assumption that “fail” means “throw an exception”, and that you don’t want any delay on the initial func call.

To test the code I used a func function that runs a countdown and throws an exception each time and finally succeeds when the countdown reaches 0.

Run this with the debug console open so you can see the console.log() messages:

function Test() {
    this.countdown = 5;

    succeeder({
        func: function () {
            console.log(
                this.constructor.name,
                'countdown:',
                this.countdown
            );
            if( this.countdown-- ) {
                console.log( 'func throwing error' );
                throw new Error( 'fail' );
            }
            console.log( 'func success!' );
        },
        context: this,
        interval: 1000,
        success: function () { return true; }
    });
};

new Test;

function succeeder(opts) {
    var func = _.bind( opts.func, opts.context );
    function run() {
        try {           
            func();
        } catch (e) {
            setTimeout( run, opts.interval );
        }       
    }
    run();      
};