SetTimeout does not work inside forEach

I have a forEach that calls a function. There should be a delay between each call. I put it inside setTimeout inside forEach. This does not correspond to a timeout after the first wait. Instead, it waits once, and then starts everything all at once. I set the timeout to 5 seconds and I use the console to confirm. 5 seconds of waiting, then several foobar console logs all at once.

Why am I getting this behavior?

var index = 0; json.objects.forEach(function(obj) { setTimeout(function(){ console.log('foobar'); self.insertDesignJsonObject(obj, index); }, 5000); }); 
+15
javascript function foreach settimeout
source share
2 answers

What Jason said is perfectly correct in his answer, but I thought I would do it to better clarify.

This is actually a classic closure problem. This usually looks something like this:

 for(var i = 0; i < 10; i++){ setTimeout(function(){ console.log(i); },i * 1000) } 

The newbie should expect the console to show:

 0 (0 seconds pass...) 1 (1 second passes...) 2 (etc..) 

But this is not so! What you actually see is the number 10 , recorded 10 times (1 time per second)!

"Why is this happening?" Great question. Closing. In the for loop above, there is no scope, since in javascript only functions (lambdas) have a limited scope!

See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

Anyway! Your attempt would achieve the desired performance if you tried this:

  json.objects.forEach(function(obj,index,collection) { setTimeout(function(){ console.log('foobar'); self.insertDesignJsonObject(obj, index); }, index * 5000); }); 

Since you have access to a "private" index variable, you can rely on the state that is expected when the function (lambda) is called!

Other sources:

How do JavaScript locks work?

http://javascript.info/tutorial/closures

http://code.tutsplus.com/tutorials/closures-front-to-back--net-24869

+16
source share

setTimeout - aync. It registers the callback function and puts it in the background, which will start after the delay. Whereas forEach is a synchronous function. So what your code did is all-in-one callbacks, each of which will be launched in 5 seconds.

Two ways to avoid this:

Have a pointer to set the timer.

 json.objects.forEach(function(obj, index) { setTimeout(function(){ // do whatever }, 5000 * (index + 1)); }); 

Thus, the delay coefficient is based on the index of your objects, so even you register them at the same time, this will work based on their own delay. index + 1 to keep the same result as in the question, since it starts at 0.

setInterval to combine objects

 var i = 0; var interval = setInterval(function(){ var obj = json.objects[i]; // do whatever i++; if(i === json.objects.length) clearInterval(interval); }, 5000); 

setInterval is similar to setTimeout, although it runs periodically based on the interval. Here we turn to the object and update the index inside the interval function. Also remember to clear the interval at the end.

The difference between these two parameters: SetInterval registers only one function compared to setTimeout registered as the number of elements in the list.

+9
source share

All Articles