31

Consider such loop:

for(var it = 0; it < 2; it++)
{
    setTimeout(function() {
        alert(it);
    }, 1);
}

The output is:

=> 2
=> 2

I would like it to be: 0, 1. I see two ways to fix it:

Solution # 1.

This one based on the fact that we can pass data to setTimeout.

for(var it = 0; it < 2; it++)
{
    setTimeout(function(data) {
        alert(data);
    }, 1, it);
}

Solution # 2.

function foo(data)
{
    setTimeout(function() {
        alert(data);
    }, 1);
}

for(var it = 0; it < 2; it++)
{
    foo(it);
}

Are there any other alternatives?

Russ Cam
  • 124,184
  • 33
  • 204
  • 266
alex2k8
  • 42,496
  • 57
  • 170
  • 221

4 Answers4

45

Not really anything more than the two ways that you have proposed, but here's another

for(var it = 0; it < 2; it++)
{
    (function() {
        var m = it;   
        setTimeout(function() {
            alert(m);
        }, 1);
    })(); 
}

Essentially, you need to capture the variable value in a closure. This method uses an immediately invoked anonymous function to capture the outer variable value it in a local variable m.

Here's a Working Demo to play with. add /edit to the URL to see the code

Russ Cam
  • 124,184
  • 33
  • 204
  • 266
  • 5
    +1. However, You can slightly modify this by changing the method signature to: `function(m) { /*code */ })(it);` – Alan Nov 24 '12 at 21:29
  • +1, but can anybody explain me why this is working?! – digory doo Sep 17 '14 at 10:06
  • 1
    @digorydoo The function declared in the loop is wrapped in parentheses followed by a set of parentheses which act to immediately invoke the function. Since variables are scoped to the function in which they are declared (or global scope if not declared within a function), the value of `it` in each iteration is assigned to the `m` variable that is scoped to the function that is executed immediately. – Russ Cam Sep 17 '14 at 10:35
  • I understand the syntax, but I'm confused why there should be a difference if the variable 'it' is copied into some other variable 'm' instead of just using 'it'. – digory doo Sep 17 '14 at 10:54
  • 1
    @digorydoo Because the value of `it` changes each iteration, if the value in each iteration is not captured in the context of the immediately executed function upon each iteration, then the value of `it` as it exists when the function passed to `setTimeout` is executed will be used. Assigning the value of `it` to `m` captures the value in each iteration in a closure, thereby alerting the expected value from each iteration when the function passed to `setTimeout` executes. Does that help? – Russ Cam Sep 17 '14 at 13:13
  • OK, I think I got it. More or less ;) – digory doo Sep 18 '14 at 11:27
20

With the let keyword you can get around this completely:

for(let it = 0; it < 2; it++)
{
    setTimeout(function() {
        alert(it);
    }, 1);
}
tbondwilkinson
  • 1,067
  • 2
  • 8
  • 16
2

Similar to above solution but self invoking inside of setTimeout function

for(var it = 0; it < 2; it++)
{
    setTimeout(function(cur) {
        return function(){
           alert(cur);
        };
     }(it), 1);
 }
Prahlad
  • 364
  • 3
  • 6
1

Similar to the other solutions, but in my opinion cleaner:

for (var it = 0; it < 2; it++) {
  // Capture the value of "it" for closure use
  (function(it) {
     setTimeout(function() {
       alert(it);
     }, 1);
  // End variable captured code
  })(it)
}

This keeps the same variable name for the capture, and does it for the entire loop, separating that from the logic of the timeout setup. If you want to add more logic inside the block, you can trivially do that.

The only thing I don't like about the solution is the repeat of "it" at the end.

Eivind Eklund
  • 208
  • 1
  • 4