Last Updated: March 21, 2023
·
163.8K
· zinkkrysty

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.

10 Responses
Add your response

for (var i = 1; i <= 5; ++i) {
    (function(n) {
        setTimeout(function(){
            console.log(n);
        }, 1000);
    }(i));
}
over 1 year ago ·

@herson I prefer your approach.

over 1 year ago ·

@herson What's the diference between your code and cristian's code? It do the same thing!!

over 1 year ago ·

@flipecoelho On the very last example, them both work the same, only that I use anonymous functions

over 1 year ago ·

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.

over 1 year ago ·

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 )

over 1 year ago ·

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

over 1 year ago ·

<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>

over 1 year ago ·

for (let i = 1; i <= 5; ++i) {
setTimeout(function(){
console.log(i);
}, 1000);
}
let will create block level scope and it will work fine.

over 1 year ago ·

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!

over 1 year ago ·