0

Please find my snippet here,

for (var i=0;i<11;i++) {
  setTimeout( () => console.log(i), 10);
}

How it print 11 for 11 times? since i was set < 11?

if i console it without function it print 1-10 .

for (var i=0;i<11;i++) {
  setTimeout( console.log(i), 10);
}

this gives me 1-10. i am wonder how its getting changed if i include function without condition?

Mohideen bin Mohammed
  • 18,813
  • 10
  • 112
  • 118
  • 2
    There's only one `i`, and when the timers fire its value is 11. – Pointy Jun 21 '17 at 18:17
  • to expand on what @Pointy said, `setTimeout` is an asynchronous function, so by the time the first log happens, the for loop has already finished running (it performs `i++` until `i<11` is false, and the first time that happens is when `i === 11`) – Patrick Barr Jun 21 '17 at 18:19
  • Because variables are references​ – Darkrum Jun 21 '17 at 18:19
  • @PatrickBarr which alos explains why it starts printing with 1. If there were no setTimeout() then it would print 0..10, not 1..11. – manassehkatz-Moving 2 Codidact Jun 21 '17 at 18:20
  • @Darkrum: That's not true in this case because `i` is a number. What's happening is that functions create closures. `i` is enclosed. – slebetman Jun 21 '17 at 18:20
  • @manassehkatz: It doesn't start printing with 1. It starts printing with 11. It prints 11,11,11 ... ten times – slebetman Jun 21 '17 at 18:21
  • @slebetman I saw a mention in the question of 1-10. Depending on timing it could be almost anything - except the "normal" 0..10. – manassehkatz-Moving 2 Codidact Jun 21 '17 at 18:29
  • 1
    Also no one has mentioned the second one will print the numbers without waiting, setTimeout will receive the result of calling console.log – ste2425 Jun 21 '17 at 18:31
  • @manassehkatz: This specific example does not depend on timing and it is not anything. It is 11, 11, 11 ... – slebetman Jun 22 '17 at 23:07

2 Answers2

2

Root case for:

for (var i=0;i<11;i++) {
  setTimeout( console.log(i), 10);
}

that console.log will be triggered directly (without any delay), so it should be:

for (var i=0;i<11;i++) {
  setTimeout(function () { console.log(i); }, 10);
}

that will give directly the same result as for ES6

Right way will be by using closures:

for (var i=0;i<11;i++) {
    ((i) => { 
        setTimeout(() => console.log(i), 10);
    })(i);
}

The reason for that that we have a single-threaded model in JavaScript. So, all setTimeout will be executed after for-cycle.

In addition it can be used let:

for (let i=1; i<=11; i++) {
    setTimeout(() => console.log(i), 10);
}
FieryCat
  • 1,875
  • 18
  • 28
  • Given the question asks explicitly about ES6, no this is not the solution. – Bergi Jun 21 '17 at 18:42
  • Hm... `setTimeout( console.log(i), 10);` - console.log will be executed outside the setTimeout. That's the case... – FieryCat Jun 21 '17 at 18:45
  • I've updated the answer – FieryCat Jun 21 '17 at 18:47
  • No, that's not what I meant. I meant you should not use IIFEs to introduce scopes in ES6. – Bergi Jun 21 '17 at 18:50
  • @Bergi, what would you suggest in that case? It's rather common solution: https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch5.md – FieryCat Jun 21 '17 at 18:57
  • See the linked duplicate. – Bergi Jun 21 '17 at 18:58
  • *"by using closures"* That is not the reason that solution works. The reason it works is that you are *creating a new scope* by *calling* a function. Whether that function is a closure or not is irrelevant. The function you are passing to `setTimeout` is also a closure but doesn't produce the desired result without the IIFE. So saying "use a closure to solve this" is not correct. – Felix Kling Jun 21 '17 at 21:19
1
for (var i=0;i<11;i++) {
  setTimeout( () => console.log(i), 10);
}
  1. Do reference for i
  2. Push on stack timeout
  3. i++
  4. Push on stack timeout
  5. i++

    ... when js has free main "thread" after execution of for loop

    10 ms after pushing timeout AND while main "thread" is free - push the first timeout to main "thread", referenced variable has value 11 because for loop is done. Do this for every timeout.

The expected output can be archived by:

for (var i=0;i<11;i++) {
  const num = i;
  setTimeout(() => console.log(num), 10);
}

Const num stores the value of i to the time of execution. After that it is garbagecollected.

Daniel Mizerski
  • 1,123
  • 1
  • 8
  • 24
  • Have you tried to execute it?) Check the last number. – FieryCat Jun 21 '17 at 18:30
  • Perfectly fine. It is returning stack. You can do `null &&` before timeout – Daniel Mizerski Jun 21 '17 at 18:35
  • you are calling `console.log` immediately on every iteration of the loop and passing the return value of that to `setTimeout` which would be undefined. Not delaying the call to console.log. Try increasing the setTimeout delay to something larger like `10000` and you will see that it isn't delaying anything. – Jonathan Kuhn Jun 21 '17 at 18:36
  • @JonathanKuhn right! sorry for mistake. Fixed – Daniel Mizerski Jun 21 '17 at 18:38
  • @DanielMizerski, check execution: http://i64.tinypic.com/forrp.png – FieryCat Jun 21 '17 at 18:42
  • 1
    @FieryCat And what is the issue? The 33 and 44 that is logged is not from the console.log, it would likely be the internal id from the last setTimeout call (return value from setTimeout). When running code in the console, it will automatically log the return value from the last called function. Aside from that, it looks perfectly fine. – Jonathan Kuhn Jun 21 '17 at 18:46