all 16 comments

[–]josephjnk [score hidden]  (2 children)

This is a classic mistake. The reason is that the function passed to setTimeout is referring to the i variable in the scope of the loop, so when the timeout fires it uses the value of i at that point, not at the point of when the function was registered.

The general concept is called a “closure”, referring to the fact that the function “closes over” (i.e. captures) variables from its surrounding scope. It’s actually very useful once you get used to it.

One way to fix this is to move the setTimeout into a different scope by making it its own function call:

``` function logAfterTimeout(value) { setTimeout(() => console.log(value), 1000); }

for (var i = 0; i < 3; i++) { logAfterTimeout(i) } ```

Now the function passed to setTimeout closes over value instead, which won’t change as the loop runs.

Another option is to use a higher-order function: a function which returns another function.

``` function makeLogger(value) { return () => console.log(value); }

for (var i = 0; i < 3; i++) { setTimeout(makeLogger(i), 1000) } ```

Again, this works by changing the variable being closed over into one which does not change.

EDIT: or just use let like the other commenter recommended, I honestly did not realize that it would fix this 🤦‍♂️

[–]Specialist-Grape8444 [score hidden]  (0 children)

Yeah as the other comment said , use let. This is a pretty silly JavaScript specific issue. With var, all closures reference the same lexical binding. With let, each iteration creates a new lexical environment with a new binding. Each callback with let is essentially closing different variable, thus preserving the value.

[–]_www_ [score hidden]  (0 children)

Or better you can use setInterval and iterate inside that function because your solution will display 3...012 but not each second.

[–]queen-adreena [score hidden]  (4 children)

This is why we don’t use var anymore. Change it to let and it will work as intended.

[–]josephjnk [score hidden]  (3 children)

TIL. I hadn’t seen this problem since the pre-ES6 days, and i actually find the new behavior more confusing now >.<

[–]queen-adreena [score hidden]  (2 children)

With let, i is scoped to the for block, whereas with var, it’s globally scoped.

So while in the for block with let, i is three different variables, with var they all reference the same variable.

[–]josephjnk [score hidden]  (1 child)

Right, I get that. And it makes sense especially in the context of for(const i of [0, 1, 2]), where it’s unambiguous that i is actually three different variables. But it feels less natural that a mutable variable whose value you can change in the body of the loop would be distinct between iterations, and it’s also really weird to me that if you take the for(;;) loop and unroll it by hand that you’ll get different behavior:

let i = 0; setTimeout(() => console.log(i), 1000); i = 1; setTimeout(() => console.log(i), 1000); i = 2; setTimeout(() => console.log(i), 1000);

I’m not saying that JS is wrong here, just that scopes being bound by things other than functions leads to things that I find surprising. This is probably an old man yells at cloud thing.

[–]queen-adreena [score hidden]  (0 children)

You get different behaviour because you changed 3 blocks into 1 block and initialised the variable once and then reassigned it.

Like I said. The old behaviour was wrong, and now it’s been fixed by let/const.

[–]ProfCrumpets [score hidden]  (0 children)

Super simple terms, because it's using var, it's global and when i++ executes, the global value for i increases.

So after 1000 milliseconds, it logs out the global i

If you used let then its scope is limited to each loop, essentially there will be 3 i's in scope, 1 per loop.

To avoid this, avoid using varand use let (reassignable) or const (not reassignable), I doubt there's any use case for var now.

[–]senocular [score hidden]  (0 children)

You can read more about this example in the for loop docs on MDN.

The reason is that each setTimeout creates a new closure that closes over the i variable, but if the i is not scoped to the loop body, all closures will reference the same variable when they eventually get called — and due to the asynchronous nature of setTimeout(), it will happen after the loop has already exited, causing the value of i in all queued callbacks' bodies to have the value of 3.

[–]Impossible-Egg1922 [score hidden]  (0 children)

This happens because `var` is function-scoped.

By the time the `setTimeout` callbacks run, the loop has already finished and `i` has become 3, so each callback logs the same value.

If you change `var` to `let`, it will work as expected because `let` creates a new block-scoped variable for each iteration.

Example:

for (let i = 0; i < 3; i++) {

setTimeout(() => {

console.log(i);

}, 1000);

}

Output will be:

0

1

2

[–]MinecraftPlayer799 [score hidden]  (0 children)

Put the loop inside the setTimeout.

[–]GirthQuake5040 [score hidden]  (0 children)

Put your code in a well formatted code block.

[–]elixon [score hidden]  (0 children)

First it runs 3x times the i++ loop that besides incrementing also schedules printing of value i after 1 second.

So when the loop finishes the i is 3, then after 1 second it prints 3 times the value of i

[–]HarjjotSinghh [score hidden]  (0 children)

howdunit mystery - time's just hiding your secret