0

I am facing an issue with a for-loop running a setTimeout.

for (var x = 0; x < 5; x++) {
    var timeoutFunction = function() {
        return function() {
            console.log(x)
        }
    }
    setTimeout(timeoutFunction(), 1)
}

I expect the output

0

1

3

4

Yet, for some reason they all output 5.

Variable x is defined in the local scope of the for-loop, so I thought this may not count for the callback of setTimeout. I tested with defining x outside of the for-loop.

var x = 10
for (var x = 0; x < 5; x++) {
    var timeoutFunction = function() {
        return function() {
            console.log(x)
        }
    }
    setTimeout(timeoutFunction(), 1)
}

I figured this output would be giving 10, yet it didn't. Then I thought it would make sense to define x afterwards.

for (var x = 0; x < 5; x++) {
    var timeoutFunction = function() {
        return function() {
            console.log(x)
        }
    }
    setTimeout(timeoutFunction(), 1)
}
var x = 10

This does return only 10. This implies the callbacks are all called after the for-loop is executed? And why do they only conform to the parent scope of the for-loop once the variable is initialised after execution of the for-loop? Am I missing something?

I know how make this example working with

for (var x = 0; x < 5; x++) {
    var timeoutFunction = function(x) {
        return function() {
            console.log(x)
        }
    }
    setTimeout(timeoutFunction(x), 1)
}

Yet, I really wonder what is missing...

3 Answers 3

1

Note that specifying 1 as the delay value will not actually cause the function to execute after 1 millisecond. The fastest the function could execute is roughly 9 milliseconds (and that's based on the internals of the browser), but in reality, it can't run the function until there is no other code running.

As for the unexpected output, you are experiencing this behavior because the timeoutFunction includes a reference to the x variable that is declared in a higher scope. This causes a closure to be created around the x variable, such that each time the function runs it does not get a copy of x for itself, but it is sharing the x value because it is declared in a higher scope. This is the classic side effect of closures.

There are a few ways to adjust the syntax to fix the issue...

Make a copy of x and let each function use its own copy by passing x into the function. When you pass a primitive type (boolean, number, string) a copy of the data is created and that's what is passed. This breaks the dependence on the shared x scope. Your last example does this:

for (var x = 0; x < 5; x++) {
    var timeoutFunction = function(x) {
        return function() {
            console.log(x)
        }
    }
    // Because you are passing x into the function, a copy of x will be made for the
    // function that is returned, that copy will be independent of x and a copy will
    // be made upon each loop iteration.
    setTimeout(timeoutFunction(x), 1)
}

Another example that does the same thing would be needed if your timeout function wasn't returning another function (because there would be no function to pass the value to). So, this example creates an extra function:

for (var x = 0; x < 5; x++) {
  
    // This time there is no nested function that will be returned,
    function timeoutFunction(i) {       
            console.log(i);        
    }
    
    // If we create an "Immediately Invoked Funtion Expression" (IIFE),
    // we can have it pass a copy of x into the function it invokes, thus
    // creating a copy that will be in a different scope than x.
    (function(i){        
      setTimeout(function(){
        timeoutFunction(i);  // i is now a copy of x
      }, 1);
    }(x));
}

If you are working with browsers that support the ECMAScript 2015 standard, you can simply change the var x declaration in your loop to let x, so that x gets block level scope upon each iteration of the loop:

// Declaring a variable with let causes the variable to have "block-level"
// scope. In this case the block is the loop's contents and for each iteration of the
// loop. Upon each iteration, a new "x" will be created, so a different scope from 
// the old "x" is what's used.
for (let x = 0; x < 5; x++) {
    var timeoutFunction = function() {
        return function() {
            console.log(x)
        }
    }
    setTimeout(timeoutFunction(), 1)
}

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

2 Comments

Thanks for the awesome examples and links! First I couldn't fully understand the remark on closures. The reference made it more clear and then I understood exactly what you meant :) "The reason for this is that the function assigned is a closure; it consist of the function definition and the captured environment from the function's scope. Five closures have been created by the loop, but each one shares the same single environment which has a variable with changing value x. When the setTimeout callbacks are executed, accessing x at that moment causes this behaviour"
@jervtub You're welcome. Closures are typically one of the more tougher things to get your mind around and they take a bit of practice to understand. Don't forget to mark as the answer. Good luck!
1

You have to create new scope for your function to make it 'remember' value from given iteration:

for (var x = 0; x < 5; x++) {
    var timeoutFunction = (function(x) {
        return function() {
            console.log(x)
        }
    })(x)
    setTimeout(timeoutFunction(), 1)
}

Another solution is to use ES2015 let:

for (let x = 0; x < 5; x++) {
    var timeoutFunction = function() {
            console.log(x)
        }
    setTimeout(timeoutFunction(), 1)
}

1 Comment

Thanks for your answer Bartekfr. I was really looking for specific information why the scope failed within the for-loop, therefore I haven't set this as the accepted answer. I like the reference to let x.
1

use this snippet

for(let x = 0; x < 5; x++) {
    var timeoutFunction = function() {
        console.log(x);
    };
    setTimeout(timeoutFunction, 1);
};
console.log('For loop completed');

JavaScript is not multi threaded so with your code the timeoutFunction will execute after the for loop is completed since you are using the global variable (with respect to timeoutFunction context) the final result is printed

2 Comments

Your solution is not correct: because timeoutFunction doesn't return function console.log is called immidiately with no delay. To test it increase timeout to 15000 and you will see that number are printed with no delay
Thanks Raj for your answer! Being single threaded explains for me the delay of logging after looping over 1000000 integers. :) I like the reference to let x.

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.