I'm confused with using setTimeout and the each iterator. How can I rewrite the following so that the console outputs each name after a delay of 5 seconds? Currently the code below prints all the names at once after 5 seconds. I would like to:
1) wait 5 seconds, then print kevin
2) wait 5 seconds, then print mike
3) wait 5 seconds, then print sally
var ary = ['kevin', 'mike', 'sally'];
_(ary).each(function(person){
setTimeout(function(){
console.log(person);
}, 5000);
});
There needs to be a delay between each time it is called. I've put it inside a setTimeout inside the forEach. It isn't respecting the timeout after the first wait. Instead it is waiting once, then running all at once.
setTimeout allows us to run a function once after the interval of time. setInterval allows us to run a function repeatedly, starting after the interval of time, then repeating continuously at that interval.
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.
Bookmark this question. Show activity on this post. Sure enough, clicking the button causes the browser to hang. This tells me that setTimeout() does not run on a separate thread.
setTimeout
setTimeout
+ conditional recursionsetInterval
+ conditional clearInterval
Here's each one of those fleshed out with a working example:
setTimeout
A couple notes on this one. This is kind of like starting a relay race and handing out instructions ahead of time to each runner to start at precisely 5:00 and 5:02 and 5:04, regardless of whether the person behind them finished a long time ago or hasn't yet arrived.
Also, you can't use a regular for i=0 loop
, because the for operator does not define new function scope. Thus object values set within each for loop will apply across iterations. By the time setTimeout is called, it will only use the most recent value. So we need a closure to store the value within each loop. I've used Array.prototype.forEach()
, but if you want to use the forEach implementations in jQuery or Underscore, that'll work too.
function ArrayPlusDelay(array, delegate, delay) {
// initialize all calls right away
array.forEach(function (el, i) {
setTimeout(function() {
// each loop, call passed in function
delegate( array[i]);
// stagger the timeout for each loop by the index
}, i * delay);
})
}
// call like this
ArrayPlusDelay(['a','b','c'], function(obj) {console.log(obj)},1000)
setTimeout
+ conditional recursionFor the bottom two options, we're making our own loops, so we'll have to keep track of the index ourselves, initializing at zero and incrementing throughout.
For this one, we'll a) call setTimeout
which will run once, b) evaluate the array at the index position, c) check if there are more elements in the array and if so, start over at (a).
function ArrayPlusDelay(array, delegate, delay) {
var i = 0
function loop() {
// each loop, call passed in function
delegate(array[i]);
// increment, and if we're still here, call again
if (i++ < array.length - 1)
setTimeout(loop, delay); //recursive
}
// seed first call
setTimeout(loop, delay);
}
// call like this
ArrayPlusDelay(['d','e','f'], function(obj) {console.log(obj)},1000)
setInterval
+ conditional clearInterval
NOTE: The function setInterval
will run forever once called. It's return value when initially set will provide a reference to the interval, so it is often combined with the function clearInterval
to optionally shut it off down the road
function ArrayPlusDelay(array, delegate, delay) {
var i = 0
// seed first call and store interval (to clear later)
var interval = setInterval(function() {
// each loop, call passed in function
delegate(array[i]);
// increment, and if we're past array, clear interval
if (i++ >= array.length - 1)
clearInterval(interval);
}, delay)
}
ArrayPlusDelay(['x','y','z'], function(obj) {console.log(obj)},1000)
Options 1 & 2 are risky because, once you set off that train, there's no way to call it off down the road (save closing the browser). If you have a large array or a heavy load in your delegate, it might be nice to provide some recourse if you need it. By saving the reference from setInterval
, we'll have constant access to the iterating function. We just need to return the interval object above and save it when calling our array plus delay function.
function ArrayPlusDelay(array, delegate, delay) {
var i = 0
// seed first call and store interval (to clear later)
var interval = setInterval(function() {
// each loop, call passed in function
delegate(array[i]);
// increment, and if we're past array, clear interval
if (i++ >= array.length - 1)
clearInterval(interval);
}, delay)
return interval
}
var inter = ArrayPlusDelay(['x','y','z'], function(obj) {console.log(obj)},1000)
Then if we ever want to clear it later on, just throw this in the console:
clearInterval(inter);
You could create a variable called offset
that makes the timer wait 5 seconds more for each person in the array, like so:
var ary = ['kevin', 'mike', 'sally'];
var offset = 0;
_(ary).each(function(person){
setTimeout(function(){
console.log(person);
}, 5000 + offset);
offset += 5000;
});
You could do
var ary = ['kevin', 'mike', 'sally'];
_(ary).each(function(person, index){
setTimeout(function(){
console.log(person);
}, index * 5000);
});
Without increasing the timeout
value, you would initialize all setTimeouts
with the exact same value (thats why you see what you see).
each
is usually better for things that happen immediately.
Instead, if you don't mind changing the array, you can use it as a queue:
var ary = ['kevin', 'mike', 'sally'];
setTimeout(function loop() {
console.log(ary.shift());
if (ary.length)
setTimeout(loop, 5000);
}, 5000);
It keeps on calling loop
5 seconds in the future until there's nothing left in the queue.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With