Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

setTimeout with Loop in JavaScript [duplicate]

Tags:

javascript

I have a very trivial question. For a simple loop with setTimeout, like this:

for (var count = 0; count < 3; count++) {
    setTimeout(function() {
        alert("Count = " + count);
    }, 1000 * count);
}

console gives an output like this:

Count = 3
Count = 3
Count = 3

Not sure why the output like this. Anyone could explain, please?

like image 591
Kanan Farzali Avatar asked Oct 07 '13 09:10

Kanan Farzali


3 Answers

This has to do with how scoping and hoisting is being treated in JavaScript.

What happens in your code is that the JS engine modifies your code to this:

var count;

for (count = 0; count < 3; count++) {
    setTimeout(function() {
        alert("Count = " + count);
    }, 1000 * count);
}

And when setTimeout() is being run it will first look in it's own scope after count but it won't find it so then it'll start looking in the functions that closes (this is called closures) over the setTimeout function until it finds the var count statement, which will have the value 3 since loop will have finished before the first timeout function has been executed.

More code-ily explained your code actually looks like this:

//first iteration
var count = 0; //this is 1 because of count++ in your for loop.

for (count = 0; count < 3; count++) { 
    setTimeout(function() {
        alert("Count = " + 1);
    }, 1000 * 1);
}
count = count + 1; //count = 1

//second iteration
var count = 1;

for (count = 0; count < 3; count++) {
    setTimeout(function() {
        alert("Count = " + 2);
    }, 1000 * 2);
}
count = count + 1; //count = 2

//third iteration
var count = 2;

for (count = 0; count < 3; count++) {
    setTimeout(function() {
        alert("Count = " + 3);
    }, 1000 * 3);
}
count = count + 1; //count = 3

//after 1000 ms
window.setTimeout(alert(count));
//after 2000 ms
window.setTimeout(alert(count));
//after 3000 ms
window.setTimeout(alert(count));
like image 103
Henrik Andersson Avatar answered Oct 20 '22 02:10

Henrik Andersson


think about it like that:

AFTER the 1000*n miliseconds are over, what will be the value of count?

of course it will be 3, because the foor loop ended way earlier than the timeout of 1000*n ms.

in order to print 1,2,3 you'll need the following:

for (var count = 0; count < 3; count++) {
    do_alert(num);
}

function do_alert(num) {
    setTimeout(function() {
        alert("Count = " + num);
    }, 1000 * num);
}

a different approach is to make it a closure function (explained well in JavaScript closures vs. anonymous functions)

for (var count = 0; count < 3; count++) {
    (function(num){setTimeout(function() {
        alert("Count = " + num);
    }, 1000 * num)})(count);
}

these two code samples will actually work similarly.

the first sample calls a named function (do_alert) each iteration.

the second sample calls a CLOSURE anonymous function (which is just like do_alert) each iteration.

it's all a matter of SCOPE.

hope that helps.

like image 4
geevee Avatar answered Oct 20 '22 02:10

geevee


This is to do with closure scoping. The same variable count is available in the scope for each of the setTimeout callback functions. You are incrementing its value and creating a function, but each instance of the function has the same variable count in its scope, and by the time the callback functions execute it will have the value 3.

You need to create a copy of the variable (e.g. var localCount = count) inside a new scope in the for loop to make this work. As for doesn't create a scope (which is the cause of the whole thing) you need to introduce one with a function scope.

e.g.

for (var i = 0; i < 5; i++) {
  (function() {
    var j = i;
    setTimeout(function() {
      console.log(j)
    },
    j*100);
   })();
 }
like image 1
Joe Avatar answered Oct 20 '22 03:10

Joe