0

When I run the following code in console:

for(var k = 0; k < 36; k++){
    setTimeout(function(k){ alert(k)}, k*5000);
}

The alert shows undefined. Moreover, I expect the delay of timeout function to be increased after every iteration; but that doesn't happen. The timeout function should run first after 5sec then after then 10sec and then 15sec and so on.

Why is undefined alerted and why isn't delay increased after each iteration?

Since k in the local scope of timeout function, it should be visible inside it.

John Doe
  • 1,364
  • 1
  • 12
  • 19
user31782
  • 7,087
  • 14
  • 68
  • 143
  • `k` as function parameter shadows the `k` variable. – gcampbell Aug 01 '16 at 13:16
  • The parameter `k` in your function has nothing to do with the `k` of the loop and as no parameter is passed to the (default) `setTimeout()` callback, it will be `undefined`. As for the timing: you just schedule the timeout to trigger after 5s, 10s, 15 starting NOW and not after one another. – Sirko Aug 01 '16 at 13:17
  • `for(var k = 0; k < 36; k++){ setTimeout(function(k){ alert(k)}, k*5000,k); }` – Rayon Aug 01 '16 at 13:17
  • @Rayon That alerts correct value of `k`. But the alert pops after every 5sec, not after 15, 10, 15, 20...` secs. – user31782 Aug 01 '16 at 13:19
  • Yeah.. Ignore that comment.. __T.J__ has provided detailed answer... – Rayon Aug 01 '16 at 13:20

3 Answers3

6

It's undefined because the timer mechanism setTimeout hooks the function to doesn't call the function you give it with any arguments (by default), and you've declared k as the function argument. When you call a JavaScript function with fewer arguments than it declares, the value of those arguments is undefined. So the argument k shadows (hides) the loop variable k, and you always see undefined.

To fix it, don't declare k as a function argument:

for (var k = 0; k < 36; k++){
    setTimeout(function(){ alert(k)}, k*5000);
    // No k here -------^
}

Example (using 500 instead of 5000):

for (var k = 0; k < 36; k++){
    setTimeout(function(){ console.log(k)}, k*500);
    // No k here -------^
}

But, then you'll have to fix a new problem (the one addressed by this question and its answers): The value of k that all of those callbacks see will be the same (36), because they see k's value as of when they're called (later, once the loop is over), not when they're created.

In ES5 and earlier, I would solve that like this:

function createHandler(k) {
    return function(){ alert(k)};
}
for (var k = 0; k < 36; k++){
    setTimeout(createHandler(k), k*5000);
}

Example:

function createHandler(k) {
    return function(){ console.log(k)};
}
for (var k = 0; k < 36; k++){
    setTimeout(createHandler(k), k*500);
}

...although many would create that createHandler function repeatedly in the loop:

for (var k = 0; k < 36; k++){
    setTimeout(function(innerk) {
        return function() { alert(innerk); }
    }(k), k*5000);
}

Example:

for (var k = 0; k < 36; k++){
    setTimeout(function(innerk) {
        return function() { console.log(innerk); }
    }(k), k*500);
}

In ES2015+ ("ES6" and above), I'd solve it like this:

for (let k = 0; k < 36; k++){
    setTimeout(() => { alert(k); }, k*5000);
}

...because when you use let within the for like that, it creates a new k for each iteration with a value that doesn't change.

Example:

// REQUIRES ES2015+ SUPPORT
for (let k = 0; k < 36; k++){
    setTimeout(() => { console.log(k); }, k*500);
}
Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • In ES6 I would solve like this `for (let k = 0; k < 36; k++){ setTimeout(()=> alert(k), k*5000);` } – The_ehT Aug 01 '16 at 13:22
  • Wen `k` is declared as the function argument. Why doesn't alert pops with `36` as it's value? – user31782 Aug 01 '16 at 13:24
  • @user31782 see the condition passed `k<36`.. the loop end when k=35 – The_ehT Aug 01 '16 at 13:25
  • 1
    @user31782: Because the function argument *shadows* (hides) the loop variable. Since the function isn't called with any arguments by the timer stuff, the value you see for the argument is `undefined`. But that still shadows the `k` loop variable. – T.J. Crowder Aug 01 '16 at 13:25
2

You can pass the k parameter in 2 ways:

for(var k = 0; k < 36; k++){
    setTimeout(function(k){ alert(k); }, k * 5000, k); // but is not supported in IE9 and earlier
}

or better wrap it into a function call:

for (var k = 0; k < 36; k++) _setTimeout(k);

function _setTimeout(k) {
    setTimeout(function(){ alert(k); }, k * 5000);
}
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
Dario
  • 3,905
  • 2
  • 13
  • 27
-1

you pass k to the callback function of setTimeOut but it takes nothing.So removing the parameter k will work.

for(var k = 0; k < 36; k++){
setTimeout(function(){ alert(k)}, k*5000);
}
tsadkan yitbarek
  • 1,360
  • 2
  • 11
  • 28
  • Without wrap or bind the **k** value to the `setTimeout` callback, you are sharing his scope, and it will use the current value of **k**, and the alert will always show "36" (see full example: http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example). – Dario Aug 02 '16 at 07:46
  • 2
    I didn't downvote. Though your solution prints `36` after each iteration. We need function closure to save different values of `k`. – user31782 Aug 04 '16 at 10:51