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?

0

4 Answers 4

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

Sign up to request clarification or add additional context in comments.

6 Comments

+1. However, You can slightly modify this by changing the method signature to: function(m) { /*code */ })(it);
+1, but can anybody explain me why this is working?!
@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.
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'.
@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?
|
21

With the let keyword you can get around this completely:

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

2 Comments

but there is not keyword named let in javascript it is in typescript i think
@PardeepJain the let keyword has been introduced in the ES6 version of JS.
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);
 }

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.