17

Possible Duplicate:
Javascript closure inside loops - simple practical example

Seen many posts talking about setTimeout and closures but I'm still not able to pass in a simple for loop counter.

for (i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i);
  }, Math.floor(Math.random() * 1000));
}

Gives

5
5
5
5
5

Would like to have

0
1
2
3
4

What's wrong ?
Please don't flame, I thought I have understood the setTimeout() tale but apparently not.

Community
  • 1
  • 1
Pierre de LESPINAY
  • 44,700
  • 57
  • 210
  • 307

3 Answers3

18

You can use a closure to keep a reference to the current value of i within the loop:

for (i = 0; i < 5; i++) {
    (function(i) {
        setTimeout(function () {
            console.log(i);
        }, Math.floor(Math.random() * 1000));
    })(i); //Pass current value into self-executing anonymous function
}​

However, this is unlikely to print the numbers in order since you use a random timeout (you could use i * 1000 instead to make the numbers print in ascending order, one second apart).

Here's a working example.

James Allardice
  • 164,175
  • 21
  • 332
  • 312
  • Is there a reason to re-use the name 'i' for the function parameter? I noticed that @JamieDixon does the same thing. To me that obscures the fact that they are completely separate variables and makes it more complicated to understand. Forked your jsfiddle as an example: http://jsfiddle.net/RnN6r/ – Jeremy Wiebe Jun 19 '12 at 12:35
  • @JeremyWiebe - No, there's no reason, you can use any identifier you like. I tend to use the same name because I find it clearer, but each to their own. – James Allardice Jun 19 '12 at 12:38
4

You need to pass i to the function being used in the setTimeout. By the time your first method is executed, i is already set to 5.

Since your timeout is variable due to the call to Math.Random(), the timeouts will be different and you won't get them in the sequence you're expecting.

Here's a working example

for (i = 0; i < 5; i++) {
    (function(i) {
        setTimeout(function () {
            console.log(i);
        }, 1000);
    })(i);
}

Changing Math.floor(Math.random() * 1000) to simply 1000 ensures that the functions execute in the order you're expecting.

Jamie Dixon
  • 53,019
  • 19
  • 125
  • 162
3

You need to wrap the "interesting" code in a function that closes over i and copies it in a local variable:

for (i = 0; i < 5; i++) {
  (function() {
    var j = i;
    setTimeout(function () {
      console.log(j);
    }, Math.floor(Math.random() * 1000));
  })();
}

The "intermediate" function call forces the value of j to be fixed at the point that function is called, so within each setTimeout callback the value of j is different.

Jon
  • 428,835
  • 81
  • 738
  • 806