0

A common pitfall with JavaScript closures is running setTimeout() from a for loop, and expecting the counter to be passed with different values at each iteration, while in practice it gets assigned the last value before the setTimeout() functions execute:

for (i = 0; i < 10; i++) { setTimeout(function () { console.log(i) }, 100); } // => prints "10" 10 times

One solution to this is to have an Immediately Invoked Function Expression:

for (i = 0; i < 10; i++) (function(j) { setTimeout(function foo() { console.log(j) }, 100); })(i); // prints 0, 1, 2, 3, 4, 5, 6, 7, 8, 9

Another is to pass an extra callback argument to setTimeout() (which doesn't work in IE<9):

for (i = 0; i < 10; i++) { setTimeout(function foo(n) { console.log(n) }, 100, i); }

But why does the following, simplest, code, produce the same result (0, 1, 2, ... 9)?

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

3 Answers 3

2

This apparently surprising behavior occurs because the first parameter to setTimeout can be a function as well as a string, the latter being eval()-ed as code.

So setTimeout(console.log(i), 100); will execute console.log(i) right away, which returns undefined. Then setTimeout("", 100) will be executed, with a NOP call after 100ms (or optimized away by the engine).

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

2 Comments

Oops, deleted my answer. I misread yours first, but it's correct. Might just be worth pointing out that the output the OP is seeing is immediate, while still in the for loop itself, and long before the 100ms delay.
Thanks @jmar777, I'm the OP :) I've included your note in the answer. Happy to delete both our comments if you'd like.
0

Just for grins, another thing you can do (when you've got .bind()) is

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

A little bit less of a mess than an IIFE.

2 Comments

Oh Pointy, you should know better. This is not an answer. This is a comment.
@slebetman posting code in a comment is essentially impossible :)
0

why does the following, simplest, code, produce the same result (0, 1, 2, ... 9)?

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

Because it actually doesn't. If you look closely, you will notice that the log messages will not need the tenth of a second before they appear in console. By calling the console.log(i) right away, you are only passing the result of the call (undefined) to setTimeout, which will do nothing later. In fact, the code is equivalent to

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

You will notice the difference better if you replace the 100 by i*500 in all your snippets, so that the log messages should be delayed at an interval of a half second.

3 Comments

@DanDascalescu: Thanks, feel free to upvote and accept :-) In your answer you say something about strings however, which I'm not sure how it is relevant to the problem.
I was linking to the MDN docs on setTimeout, which explain that the first parameter can be either a function or a string, which is eval()ed. I believe setTimeout() converts the undefined returned by console.log() to en empty string, thus executing nothing after the timeout (or not even setting a timer).
Yes, that's what I would expect: If the argument is not a function (nor a string), then nothing is happening at all.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.