1

Alright, first ever stack overflow question. I hope I do this right.

I'm trying to run this code:

    for(var i = 1; i < 17; i++){
    console.log("for loop runs. i is " + i);
    setTimeout(function(){
        console.log("setTimeout runs. i is " + i);
        if(i < 3){
            $( ".example1" ).append( i );
            $( ".example2" ).append( i );
            $( ".example3" ).append( i );
            $( ".example4" ).append( i );
            $( ".example5" ).append( i );
        }
        else if(i<5){
            $( ".example1" ).append( i );
            $( ".example2" ).append( i );
            $( ".example3" ).append( i );
            $( ".example5" ).append( i );
        }
         else if(i<11){
            $( ".example1" ).append( i );
            $( ".example2" ).append( i );
            $( ".example3" ).append( i );
        }
        else if(i<15){
            $( ".example1" ).append( i );
            $( ".example3" ).append( i );           
        }
        else if(i<17){
            $( ".example1" ).append( i );
        }
    },200);
} //end for loop

And I'm getting this output in the console:

    for loop runs. i is 2
    for loop runs. i is 3
    for loop runs. i is 4
    for loop runs. i is 5
    for loop runs. i is 6
    for loop runs. i is 7
    for loop runs. i is 8
    for loop runs. i is 9
    for loop runs. i is 10
    for loop runs. i is 11
    for loop runs. i is 12
    for loop runs. i is 13
    for loop runs. i is 14
    for loop runs. i is 15
    for loop runs. i is 16
    (16) setTimeout runs. i is 17 // <---- runs 16 times

So the for loop runs and increments up to 17, then setTimeout runs 16 times. I don't understand what's going on here.

T Ainsley
  • 13
  • 3

3 Answers3

1

Solution to: Variable Not Having Expected Value When setTimeout Executes

Variable closure issue. IIFE to the rescue:

for(var i = 1; i < 17; i++){
  console.log("for loop runs. i is " + i);
  (function (ii) {
    setTimeout(function(){
      console.log("setTimeout runs. ii is " + ii);
      // the rest of your code goes here
    },200);
  }(i));
}

Explanation:

The reason you're getting the value "17" for i is because by the time the setTimeouts execute, i has been assigned the value "17" with the final iteration of the for loop. (i has to get set to "17" for the loop to terminate.) When you're handing i to the setTimeout function you're giving it a reference to i, not the current value of i.

Variable scopes are determined by the functions they are instantiated (var'd) in and upon execution of that function. i is (automatically) var'd upon preparing the for loop. So What "function" does the for loop live in? window, or the global scope. That is the scope of i. That is where setTimeout goes to get the value of i when it fires.

So, to solve the problem we need to instantiate a variable within a function and execute that function to lock in the value of that variable at that moment in the execution of the program.

Conceptually speaking, first, write a function, with one or more parameters:

function (x, y, z) {
  // stuff here
};

x, y, and z get var'd within this function scope when this function is executed.

Second, execute the function, passing necessary parameters:

function (x, y, z) {
  // stuff here
}(a, b, c);

a, b, and c have already been var'd, likely in the global scope, and now you're passing their current values to this function as parameters.

And third, wrap the function in parenthesis so the JavaScript interpreter treats it as a function expression and not a function declaration.

(function (x, y, z) {
  // stuff here
}(a, b, c));

By the way, this works, too:

!function (x, y, z) {
  // stuff here
}(a, b, c);

...as does ~, +, and -. You just need some benign operator at the beginning of the line to slap JS out of it's function declaration frenzy.

Kinda like the for loop, function parameter variables are automatically var'd when the function is executed, but in this case the scope is within this function itself. So the value of i at the moment of execution in that line of code gets passed to the new variable ii inside the IIFE function and locked in to that function scope. Now, when setTimeout is finally ready to execute it looks to that function scope, rather than global, to get the value.

bloodyKnuckles
  • 11,551
  • 3
  • 29
  • 37
0

Read more about closures in javascript. What is happening here is that multiple functions (closures) are created inside for loop all sharing same environment. By the time setTimeout function is called loop is finished iterating and i variable is always set to 17.

Read more here

Zeljko
  • 41
  • 2
0

for loop runs 16 times, for i = 1 to 16 (as expected). your setTimeout runs also 16 times as it inside same for cycle.

because your setTimeout function runs after 200 miliseconds, but 16 iterations of for cycle runs in fraction of that time, all setTimeouts runs long after for cycle ended when i is already set to be 17.

Ján Stibila
  • 619
  • 3
  • 12
  • 1
    I don't think the second paragraph of your answer is correct. The setTimouts will always run at the end because the loop keeps the single thread busy. Try it with more iterations and a shorter timout – Lee Bailey Mar 18 '15 at 14:37
  • And I don't think, that you are wrong :) – Ján Stibila Mar 18 '15 at 14:50