If you’ve ever programmed something in JavaScript, you most likely ran into a situation where you needed a delay.
Normally, we do this with setTimeout().
For repeatedly calling some function every X milliseconds, one would normally use setInterval().
Well, that’s fine. But what if you want to so something 10 times, and delay 3 seconds between iterations?
The solution is not as obvious as it appears…
But it is simple!
We’re going to just use the following code as our “function”:
alert("Cheese!");
Normally you would probably want to do something a bit more complicated than that, but we’ll just keep things simple for the sake of the example.
Now, most likely, in order to alert “Cheese!” ten times (with a 3-second delay between each alert), the first thing you’ll probably try is this:
for (var i = 1; i < 10; i++) { setTimeout(function () { alert("Cheese!"); }, 3000); }
The above code will not do what you want, though. JavaScript executes linearly, one line after another. But it also has a stack, or a queue of operations to perform. The code works like so:
- The setTimeout function is queued immediately for future execution (after 3 seconds)
- The for loop moves on to the next iteration right away
- The setTimeout function is again queued immediately for future execution (after 3 seconds)
… and so on, up to 10 times.
So, what’s the problem?
Well, the queuing of the setTimeout function and the for loop iterations happen “instantly”, or nearly so. That means that within milliseconds, all 10 of your alert(“Cheese!”); functions are queued up and ready to fire – one after the other, about 3 seconds in the future.
Instead of seeing 1 alert message every 3 seconds, what you get is a 3-second delay followed by 10 alert messages in quick succession!
Visually, you’re getting this:
And that’s not what you want. So, what to do?
Behold:
(function theLoop (i) { setTimeout(function () { alert("Cheese!"); if (--i) { // If i > 0, keep going theLoop(i); // Call the loop again, and pass it the current value of i } }, 3000); })(10);
First of all, we’re using a self-invoking function. The last line calls the function theLoop, and passes it the value 10 for i.
Next, the setTimeout function will call the code inside after 3 seconds have passed. But notice the code included after the alert…
There is a check that says if we decrement i, and it’s still greater than 0, we should call theLoop(i) again. Note that this if statement and the subsequent calling of theLoop is not queued instantly, because all the code inside the setTimeout will only execute after 3 seconds has elapsed. So, this code works like so:
- The setTimeout function is queued immediately for future execution (after 3 seconds)
- After 3 seconds, the alert fires, we decrement i, and we call theLoop again, which queues the next setTimeout function
… and so on, up to 10 times.
That’s exactly what we want: 10 alerts, and each alert pops up 3 seconds after the previous one.
Ta-DA!
If you’d like to get really fancy, you can do this:
(function theLoop (data, stuff, i) { setTimeout(function () { alert("Cheese!"); // DO SOMETHING WITH data AND stuff if (--i) { // If i > 0, keep going theLoop(data, stuff, i); // Call the loop again } }, 3000); })(data, stuff, i);
If any of this is confusing, or if you’d really like to understand more about some of the pitfalls and complexities of JavaScript, you seriously need to read K. Scott Allen’s excellent explanations of things like this, Function.apply vs. Function.call, variable scopes, and closures in JavaScript:
- JavaScript’s Slippery this Reference and Other Monsters in The Closet
- Function.apply and Function.call in JavaScript
- Putting Function.apply() to Work
- Closure On JavaScript Closures
And finally, don’t miss John Resig on How Javascript Timers Work!
Awesome tutorial that made my day! Thought that was happening with the que but wasn’t sure how to fix.
Hi
Thank you for this tutorial.
I kind of got it but it quickly turned into a mess in my head.
So I implemented it “the hacking way” into my code and it performed like a charm. (even though I had to mark the perimeter of this code with “don’t touch” comments)
Now the time to pay the technical dept is coming, as I have to turn this code into Java for I am porting my mini game on smartphone 🙂
As far as I got, the answer is ” Thread.sleep ” but I have a feeling it is not what I am looking for.
Could you show me the direction to the Java way of this tutorial ?
thx again 🙂
EEK!! I haven’t programmed anything in Java in a loooong time. I’m afraid I can’t help you there.
Very well written and explained. Thank you!
Hi,
I have been trying to use the “Se;f-Invoking” function inside of the loop but it doesn’t work as expected. Do you have any example?
Thanks.
Usually, that’s due to variable scope. Both “this” and other variables do non-intuitive things in JavaScript. Well, non-intuitive to me, anyway… Maybe this might help: https://www.sitepoint.com/demystifying-javascript-variable-scope-hoisting/
Self-invoking functions are particularly tricky. Unfortunately, I don’t have a non-proprietary example to show you, but struggling is good!
BTW, if you like jQuery, check out my JS library Pika: https://scottiestech.info/2017/10/03/pikajs-think-jquery-but-smaller-lighter-and-just-as-easy-to-use/
I use it for all my projects now, and I have yet to find an issue with it in any browser.
Sorry, my bad. I was using the function insider the loop and not vice-versa. Here is my working example:
// Sorting Last to First
function sortlastToFirst() {
var parElement = document.getElementById(“flex-box”);
// Target every child of the element
var numChildren = parElement.children.length;
(function myLoop(i) {
setTimeout(function() {
alert(“hell88o”); // your code here
for (let index = 0; index 0
}, 3000);
})(5); // pass the number of iterations as an argument
}
Thank you
Aha! Awesome…
I loved this. I ended writing something like this:
function createProperty(x, y) {
console.log(`x: ${x}, y: ${y}`);
}
(function childLoop (x) {
setTimeout(function () {
(function childWeekLoop (y) {
setTimeout(async function () {
await createProperty(x, y);
if (--y) {
childWeekLoop(y);
}
if (y==0 && --x) {
childLoop(x);
console.log(`----------`);
}
}, 1000);
})(8);
}, 1000);
})(3);