Be careful with setTimeout in loops
setTimeout
is a great tool in JavaScript, but it has its drawbacks and problems you should be aware of:
There isn't a cross-browser way of passing a timeout callback with arguments. Only modern browsers support the following syntax:
setTimeout(myFunction, 1000, myVariable);
There are workarounds that look like this:
setTimeout(function(){
myFunction(myVariable);
}, 1000);
but this leads to the following problem:
The variables you're passing in don't keep their initial values, so if you're changing them (as in a for loop) please be aware:
for (i = 1; i <= 5; ++i) {
setTimeout(function(){
console.log(i);
}, 1000);
}
This will output the value 6
five times, which is not the intention.
Fortunately, there's a workaround for this
Set the timeout from within a function
for (i = 1; i <= 5; ++i) {
setDelay(i);
}
function setDelay(i) {
setTimeout(function(){
console.log(i);
}, 1000);
}
And there you have it! It's also a bit more elegant for larger projects.
Written by Cristian Andrei
Related protips
10 Responses
for (var i = 1; i <= 5; ++i) {
(function(n) {
setTimeout(function(){
console.log(n);
}, 1000);
}(i));
}
@herson I prefer your approach.
@herson What's the diference between your code and cristian's code? It do the same thing!!
@flipecoelho On the very last example, them both work the same, only that I use anonymous functions
Ran this through jsperf to get a quick comparison op-wise http://jsperf.com/anonymous-vs-named-settimeout-in-a-loop, anonymous is marginally quicker (although it might of been worth upping the number of loops) but the anonymous also has the edge on keeping the namespace tidy and, for me at least, is a slightly more elegant solution.
It's keeping the namespace tidy that gives it my nod.
Figuring out performance heuristics in JIT implementations is hard:
http://jsperf.com/anonymous-vs-named-settimeout-in-a-loop/2
(I'm a pythonista, so I prefer naming my functions anyways :P )
I'd say you're half-right, but adding a proper closure and applying the context or arguments would be a bit cleaner.
Check out this SO post for details:
http://stackoverflow.com/questions/1728563/changing-the-scope-of-an-anonymous-function-on-a-settimeout-causes-a-weird-warni
<html>
<head></head>
<body>
Using setTimeout in the example loop will not behave as expected, if you expect that there will be a one second interval between each task. What happens instead is the whole loop is executed in under a millisecond, and each of the 5 tasks is scheduled to be placed on the synchronous JS event queue one second later. They will run consecutively in order, but not each separated by a second.
The desired behavior can be realized by using so-called "recursive" syntax, e.g., by calling the next setTimeout from inside the cllback function itself. Actually it is not true recursive because the calling function returns before the new scheduled task runs, so the stack does not increase in size. This is important because you would never run out of stack space using the so-called "recursive" syntax.
I have created a
<a href="https://jsfiddle.net/craigphicks/702p020d/">short program on jsfiddle</a>
to demonstrate the above behaviors.
</body>
</html>
for (let i = 1; i <= 5; ++i) {
setTimeout(function(){
console.log(i);
}, 1000);
}
let will create block level scope and it will work fine.
for (i = 1; i <= 5; ++i) {
setDelay(i);
}
function setDelay(i) {
setTimeout(function(){
console.log(i);
}, 1000 * i);
}
This frustrated me, in my case I needed the '* i' as shown above. Hope this helps anyone else suffering the same problem!