Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript - setTimeout Closure Issue

I'm struggling with the code below. I have tried many different ways of doing this but I end up with one of two incorrect results.

for(i = 0; i < result.length; i++) {

    var tmpBlockInfo = {
        size: worldTest.data[0].size,
        xStartPixel :  result[i].x * worldTest.data[0].size,
        yStartPixel : result[i].y * worldTest.data[0].size,
        blockType : (Math.random() * 100 > 10) ? 'path' : 'wall'
    }

    var tmpFunc = function(){
        worldTest.fillBlock(tmpBlockInfo, 157, 152, 124,  255)
    };

    var t = setTimeout(function(){
        tmpFunc()
    } , 500 * i);
}

The problem with the above code is tmpBlockInfo always gets the last result[i].x / result[i].y. So i'm asuming when the timeout runs the function its seeing what result[i].x / result[i].y was left on after the loop (rather than passing it as a "new" variable)

I thought putting it into a function would fix the closure problem, but no luck.

Also tried:

for(i = 0; i < result.length; i++) {

    var tmpBlockInfo = {
        size: worldTest.data[0].size,
        xStartPixel :  result[i].x * worldTest.data[0].size,
        yStartPixel : result[i].y * worldTest.data[0].size,
        blockType : (Math.random() * 100 > 10) ? 'path' : 'wall'
    }

    var t = setTimeout(function(){
        worldTest.fillBlock(tmpBlockInfo, 157, 152, 124,  255)
    } , 10000 * i);
}

With the same results as the first code.

If I do:

for(i=0; i < result.length; i++) {

    var tmpBlockInfo = {
        size: worldTest.data[0].size,
        xStartPixel :  result[i].x * worldTest.data[0].size,
        yStartPixel : result[i].y * worldTest.data[0].size,
        blockType : (Math.random() * 100 > 10) ? 'path' : 'wall'
    }

    setTimeout(function(passBlockInfo) {
        worldTest.fillBlock(tmpBlockInfo, 157, 152, 124,  255) 
    } (tmpBlockInfo), 1000 * i);
}

It does process all the fillBlock functions correctly. BUT it does them all at the same time (e.g. it's not firing them one at a time. It's just doing them after each other but causing blocking (no screen update) and no delay between each one.

Any help with this would be great!

like image 740
james Avatar asked Feb 28 '12 15:02

james


1 Answers

The reason is it is executing them right away is because you are executing the function in the setTimeout call. What i would do is create another function like so

function MakeTimeoutCall(fn, data, timeout){
    setTimeout(function() {fn.call(null, data);}, timeout);
}

Then in your loop where you call setTimeout, do this

MakeTimeoutCall(
    function(passBlockInfo){
       worldTest.fillBlock(passBlockInfo, 157, 152, 124,  255);
    },
    tmpBlockInfo,
    1000 * i);

(Assuming worldTest is a global object).

That should work. setTimeout is expecting a function to call at the end of the timeout, you are giving your function, but calling it right away. The return value, in this case null, is then given to the timeout. So there is no timeout, everything happens at once.

Just in case my answer is a little complicated given the context, here is a link to a simpler solution in jsfiddle.

like image 90
Zoidberg Avatar answered Sep 21 '22 23:09

Zoidberg