Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

setTimeout in for-loop does not print consecutive values [duplicate]

Tags:

javascript

I have this script:

for (var i = 1; i <= 2; i++) {     setTimeout(function() { alert(i) }, 100); } 

But 3 is alerted both times, instead of 1 then 2.

Is there a way to pass i, without writing the function as a string?

like image 679
Ilyssis Avatar asked Mar 07 '11 22:03

Ilyssis


People also ask

Can I use setTimeout in for loop?

The setTimeout function callback isn't triggered until the for loop execution has completed. When the for loop has finished executing the value of i is 5. Now when the setTimeout call begins to execute it uses the last set value of i which is 5. Hence 5 is printed in all the setTimeout callbacks.

Is setTimeout synchronous or asynchronous?

setTimeout is asynchronous - but it does not have a Promise -API. The basic functionality of asynchronous functions is not Promises , but Callbacks (meaning giving a function that gets called asynchronously .

Does setTimeout work synchronously?

setTimeout is asynchronous: setTimeout is asynchronous, so the last line will not wait for setTimeout.

Does setTimeout work asynchronous?

Working with asynchronous functionssetTimeout() is an asynchronous function, meaning that the timer function will not pause execution of other functions in the functions stack. In other words, you cannot use setTimeout() to create a "pause" before the next function in the function stack fires.


2 Answers

You have to arrange for a distinct copy of "i" to be present for each of the timeout functions.

function doSetTimeout(i) {   setTimeout(function() { alert(i); }, 100); }  for (var i = 1; i <= 2; ++i)   doSetTimeout(i); 

If you don't do something like this (and there are other variations on this same idea), then each of the timer handler functions will share the same variable "i". When the loop is finished, what's the value of "i"? It's 3! By using an intermediating function, a copy of the value of the variable is made. Since the timeout handler is created in the context of that copy, it has its own private "i" to use.

edit — there have been a couple of comments over time in which some confusion was evident over the fact that setting up a few timeouts causes the handlers to all fire at the same time. It's important to understand that the process of setting up the timer — the calls to setTimeout() — take almost no time at all. That is, telling the system, "Please call this function after 1000 milliseconds" will return almost immediately, as the process of installing the timeout request in the timer queue is very fast.

Thus, if a succession of timeout requests is made, as is the case in the code in the OP and in my answer, and the time delay value is the same for each one, then once that amount of time has elapsed all the timer handlers will be called one after another in rapid succession.

If what you need is for the handlers to be called at intervals, you can either use setInterval(), which is called exactly like setTimeout() but which will fire more than once after repeated delays of the requested amount, or instead you can establish the timeouts and multiply the time value by your iteration counter. That is, to modify my example code:

function doScaledTimeout(i) {   setTimeout(function() {     alert(i);   }, i * 5000); } 

(With a 100 millisecond timeout, the effect won't be very obvious, so I bumped the number up to 5000.) The value of i is multiplied by the base delay value, so calling that 5 times in a loop will result in delays of 5 seconds, 10 seconds, 15 seconds, 20 seconds, and 25 seconds.

Update

Here in 2018, there is a simpler alternative. With the new ability to declare variables in scopes more narrow than functions, the original code would work if so modified:

for (let i = 1; i <= 2; i++) {     setTimeout(function() { alert(i) }, 100); } 

The let declaration, unlike var, will itself cause there to be a distinct i for each iteration of the loop.

like image 171
Pointy Avatar answered Oct 12 '22 13:10

Pointy


You can use an immediately-invoked function expression (IIFE) to create a closure around setTimeout:

for (var i = 1; i <= 3; i++) {      (function(index) {          setTimeout(function() { alert(index); }, i * 1000);      })(i);  }
like image 33
Darin Dimitrov Avatar answered Oct 12 '22 12:10

Darin Dimitrov