1

Possible Duplicate:
How to Use setTimeout in a for…loop
calling setTimeout with a for loop

For me, setTimeout function doesn't work inside for loop. It executes after all for loop statements are executed.

I am facing with this scope issue in case of setTimeout function in javascript.

Here's my code snippet ..

 ... moves[] is an array ..

 for(i=0;i<noOfMoves;i++) {

        playerName = moves[i].playerName;
        timeDiff = moves[i].timeDiff;
        console.log("Inside for loop"+ playerName);

        setTimeout(function(){
             console.log("Inside set time out :"+playerName);
        },timeDiff);
 ....
 ....
}

But it awkwardly prints out the following output ...

 Inside for loopplayer1
 Inside for loopplayer2
 Inside for loopplayer3
 Inside for loopplayer4
 .... (noOfMoeves times .. )
 Inside set time outplayer1
 Inside set time outplayer1
 Inside set time outplayer1
 Inside set time outplayer1

EDIT :

I am wanting o/p of following way

I am expecting the code to go line by line .. printing "Inside for loop" console log first, then wait for "timeDiff" period and then print the "Inside settimeout" function console log .. how can i do that ? –

 Inside for loopplayer1
 Inside set time outplayer1 // (after waiting for timeDiff time)
 Inside for loopplayer2
 Inside set time outplayer2  // (after waiting for timeDiff time)
 ......
 ......

Also, playerName variable is getting same value in each settimeout console log statement ?

Community
  • 1
  • 1
user1452041
  • 23
  • 1
  • 6
  • 2
    The common closure question. Someone will find the dupe faster than me. :) – epascarello Jun 14 '12 at 16:18
  • 1
    Amazing what one can find when a person searches. My query was [javascript settimeout in for loop](http://stackoverflow.com/search?q=javascript+settimeout+in+for+loop). First result. –  Jun 14 '12 at 16:21
  • @user: Are you sure your output is *"Inside set time **outplayer1**"*, and not *"Inside set time **outplayer4**"*? –  Jun 14 '12 at 16:29
  • @all : I want to use that `playerName` variable inside the settimeout function .. what should i do ? – user1452041 Jun 14 '12 at 16:30
  • @am not i am : Yes, that `playerName` variable is getting a value of "player1" inside settimeout fn – user1452041 Jun 14 '12 at 16:31
  • @all : Please check the edited o/p that i am expecting .. – user1452041 Jun 14 '12 at 16:38
  • If you want all of the output delayed, then put it all in the `setTimeout`. If you want no delay, then don't use `setTimeout`. –  Jun 14 '12 at 16:40
  • @user1452041, There are two issues here. One is the fact that you need to use a closure to address the scope issue with setTimeout. The other is the fact that setTimeout will not execute until after the for loop is finished. Please see my answer. – xcopy Jun 14 '12 at 16:40
  • @am not i am: I am expecting the code to go line by line .. printing "for loop" console log first, then wait for "timeDiff" period and then print the "Inside settimeout" function console log .. how can i do that ? – user1452041 Jun 14 '12 at 16:42
  • @user1452041: A `setTimeout` does not pause the script. It asynchronously delays the execution of the code you pass it. All other code runs synchronously. –  Jun 14 '12 at 16:43
  • Then how can i achieve step by step sequential o/p as i have shown in my expected o/p in question ? – user1452041 Jun 14 '12 at 16:46
  • There are a couple solutions. I'll post an answer in a minute. –  Jun 14 '12 at 16:47
  • Please give me a solution. I was banging my head since past 4 hours over this issue .. – user1452041 Jun 14 '12 at 16:51
  • @user1452041, please take a look at my answer. I think that it resolves all of your issues. – xcopy Jun 14 '12 at 16:58

3 Answers3

2

This is not entirely due to closures, this is due to the fact that javascript is single threaded and set Timeout will not take place until javascript has free time to execute. The for loop will always finish before setTimeout executes it's code.

To solve the issue, put everything into a setInterval like so:

var moves = [{playerName:'Test'},{playerName:'Terry'}, {playerName:'sdfsdf'}];
var currIdx = 0;
var intervalId = window.setInterval(function () {
    var playerName = moves[currIdx].playerName;
    console.log("Inside for loop"+ playerName);
    (function(name) {
        setTimeout(function(){
             console.log("Inside set time out :"+name);
        },0);
    })(playerName);
    currIdx++;
    if(currIdx >= moves.length)
        window.clearTimeout(intervalId);        
}, 10);

Please see the fiddle for a sample - http://jsfiddle.net/uTyVw/2/

xcopy
  • 2,248
  • 18
  • 24
  • It has everything to do with closures. Each function passed to `setTimeout` has closed over the same `playerName` variable. The solution is to create a new variable scope in each iteration, and have the `setTimeout` functions create a closure over a variable local in that scope. –  Jun 14 '12 at 16:32
  • 1
    Your missing the point. Closure or not, the setTimeout will not execute until after the for loop has completed. – xcopy Jun 14 '12 at 16:35
  • Why does that matter? If that's not the desired behavior, then there's no reason to use `setTimeout` in the first place. –  Jun 14 '12 at 16:37
  • @tjscience Please see the edited Q for expected o/p . – user1452041 Jun 14 '12 at 16:39
1

It's because of closures. Change your code like this:

for(i=0;i<noOfMoves;i++) {
    playerName = moves[i].playerName;
    console.log("Inside for loop"+ playerName);
    (function(name) {
        setTimeout(function(){
             console.log("Inside set time out :"+name);
        },timeDiff);
    })(playerName);
}

You can know more about closures here.

Updated code:

var moves = [
    {playerName: '1'},
    {playerName: '2'},
    {playerName: '3'},
    {playerName: '4'}
];
var timeDiff = 1000;
var currentMove = 0;

var processNextMove = function() {
    var move = moves[currentMove];
    console.log('Inside for loop: ' + move.playerName);
    currentMove++;
    window.setTimeout(function() {
        console.log('Inside set time out: ' + move.playerName);
        if(currentMove != moves.length) {
            processNextMove(); 
        }
    }, timeDiff);
};

processNextMove();
Community
  • 1
  • 1
KAdot
  • 1,997
  • 13
  • 21
  • Your code suffers from the same issue that the OP is having. – xcopy Jun 14 '12 at 16:24
  • 1
    You are not right. Please test this code http://jsfiddle.net/JuM4Z/ – KAdot Jun 14 '12 at 16:29
  • I want to use the same value of playerName inside settimeout console log also .. at the same time .. i want it to happen sequentially for every for statement .. i.e execute "Inside for loop", then "Inside set time out" `noOfMoves` times – user1452041 Jun 14 '12 at 16:34
  • @KAdot, Did you even look at the console output? The "Inside set time out..." messages do not start until after the for loop messages. – xcopy Jun 14 '12 at 16:34
  • @tjscience: Who cares. If a `setTimeout` is used, then that's clearly the intended behavior. KAdot's solution provides the correct player name for each setTimeout. That was the issue. Look at the question. –  Jun 14 '12 at 16:37
  • Read the first line of the OP's question. – xcopy Jun 14 '12 at 16:38
  • @KAdot : Please see the edited Q for expected o/p ... – user1452041 Jun 14 '12 at 16:39
  • @tjscience: *(In the original question)*, there was no indication that the delayed output was a problem. Again, if you don't want a delay, there's no reason to use `setTimeout`. –  Jun 14 '12 at 16:41
  • ...at least I didn't read that as the actual issue. Reading it again, I can see that. –  Jun 14 '12 at 16:42
  • Check my updated solution: http://jsfiddle.net/Jh5N8/ – KAdot Jun 14 '12 at 16:51
  • 1
    Thanks! You'r edited code worked like charm! ... Thank you for your help. – user1452041 Jun 14 '12 at 17:29
0

It looks like you want to step to the next item once an iteration and delay are complete. Here's a stepper that might help you.

// Generic stepper. On step will move to next item and
// run custom stepping function. If at the end will return 0.
function Stepper(stepfunc, items) {
    this.items = items;
    this.index = -1;
    this.stepfunc = stepfunc;
}

Stepper.prototype.start = function() {
    this.index = -1;
    this.step();
}

Stepper.prototype.step = function() {
    this.index++; // move to the next item

    // Stop when we reach the end.
    if (this.items.length <= this.index) {
        return 0;
    }

        /* Do something now. */
        var item = this.items[this.index];
        this.stepfunc(item);

        return 1;
}

// Custom step function.
function myStep(item) {
    // Do this now.
    console.log(item.n);

    // Get a reference to the stepper.
    var s = this;

    // Do this later.
    setTimeout(function(){
        console.log(item.n + ' after ' + item.t);
        var status = s.step();
        console.log(status);
    }, item.t);
}

var items = [{n: 'A', t: 500}, {n: 'B', t: 600}, {n: 'C', t: 1000}];
var s = new Stepper(myStep, items);
s.start();
wolfhammer
  • 2,641
  • 1
  • 12
  • 11