Setting timeout inside loop with javascript - javascript

I'm making a puzzle solving function which uses an array of puzzle pieces in their current shuffled order. Each piece has an id which points to the correct position in the array. I set overlay colors on the pieces that are about to be swapped. I want for there to be a delay between the pieces being colored and then swapped.
function solvePuzzle() {
while (rezolvat == false) // while all pieces are not in correct position
for (var i = 0; i < _piese.length; i++) { // iterates through array of puzzle pieces
if (checkPiesa(i) == false) {
_piesaCurentaDrop = _piese[i];
_piesaCurenta = getPiesaDestinatie(_piesaCurentaDrop.id); // destination piece
_context.save();
_context.globalAlpha = .4;
_context.fillStyle = PUZZLE_HOVER_TINT;
_context.fillRect(_piesaCurentaDrop.xPos, _piesaCurentaDrop.yPos, _latimePiesa, _inaltimePiesa);
_context.fillStyle = PUZZLE_DESTINATION_HOVER_TINT;
_context.fillRect(_piesaCurenta.xPos, _piesaCurenta.yPos, _latimePiesa, _inaltimePiesa);
_context.restore();
// here I want to have some kind of 'sleep' for 2000 ms so you can see the pieces about to be swapped
dropPiece(); // function for swapping puzzle pieces
}
}
}

You can use javascript's setTimeout() functions which will execute the function after specified milliseconds, you can learn more about it here
function solvePuzzle() {
while (rezolvat == false) // while all pieces are not in correct position
for (var i = 0; i < _piese.length; i++) { // iterates through array of puzzle pieces
(function (i) {
setTimeout(function () {
if (checkPiesa(i) == false) {
_piesaCurentaDrop = _piese[i];
_piesaCurenta = getPiesaDestinatie(_piesaCurentaDrop.id); // destination piece
_context.save();
_context.globalAlpha = .4;
_context.fillStyle = PUZZLE_HOVER_TINT;
_context.fillRect(_piesaCurentaDrop.xPos, _piesaCurentaDrop.yPos, _latimePiesa, _inaltimePiesa);
_context.fillStyle = PUZZLE_DESTINATION_HOVER_TINT;
_context.fillRect(_piesaCurenta.xPos, _piesaCurenta.yPos, _latimePiesa, _inaltimePiesa);
_context.restore();
// here I want to have some kind of 'sleep' for 2000 ms so you can see the pieces about to be swapped
// setTimeout in side task execution
setTimeout(() => dropPiece(), 1000); // function for swapping puzzle pieces
}
}, 2000 * i); // schedules excution increasingly for each iteration
})(i);
}
}
In the code above for loop finishes immediately, however, it schedules the execution of each iteration after a specified increased time(i*2000) using setTimeout
So the execution of the, (Despite for loop's immediate completion)
first iteration will begin immediately at 0*2000=0 mili-seconds,
the task for second execution will be executed after (1*2000),
the task for third execution will be executed after (2*2000),
and so on..
Just for a simple understanding look at the sample code below
Working Code Sample
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
setTimeout(() => console.log(i + 0.5), 1000); // setTimeout in side task execution in case needed
}, 2000 * i); // schedules excution increasingly for each iteration
})(i);
}

Related

JS die roll simulation with 6 die images

in Js, I want to try to simulate a die roll by showing images of die 1 to 6, but when I try to display these in a for loop, it only displays image dice6. I tried putting in a nested for loop to slow down the outer loop but that didnt work. Does the page need to refresh after changing "src" attribute?
const dieImage = function (num) {
return "images/dice" + String(num).trim() + ".png";
};
function dieRoll(num) {
for (let i = 1; i < 7; i++) {
for (let z = 0; z < 44444; z++) {} // attempt to slow
if (num === 1) {
img1.setAttribute("src", dieImage(i));
} else {
img2.setAttribute("src", dieImage(i));
}
}
}
As mentioned in the comments you can use setTimeout. You can introduce a delay and give the browser a chance to redraw by using setTimeout, promise, and await, for example like this:
const DELAY = 300; // 300ms
async function dieRoll(num) {
for (let i = 1; i < 7; i++) {
if (num === 1) {
img1.setAttribute("src", dieImage(i));
} else {
img2.setAttribute("src", dieImage(i));
}
await new Promise((resolve) => setTimeout(() => resolve(), DELAY));
}
}
The loop execution will stop until the promise is resolved, but it will let the browser to continue to respond and redraw. When the promise is resolved after the timeout callback is run after the given DELAY milliseconds, the next iteration of the loop will take place.
What you are missing (roughly) is that the browser paints the screen when the JavaScript code has finished running. Even though you are setting the src attribute to a different image in a loop, the JavaScript code finishes only when the loop ends. The browser paints only once, i.e. the last image you set in the loop. This explanation may be oversimplified, but it gives you an idea.
The solution is to return from the JavaScript code after setting the src and repeating after a suitable delay, giving the user the opportunity to sense the change. setTimeout is probably good enough for your case; in other use cases where you want really smooth animation, there would be other solutions (e.g. requestAnimationFrame()). An untested implementation to demonstrate the intent:
function dieRoll(selectedNum) {
var counter = 8; // how many times to change the die
function repeat() {
if (counter === 1) {
// on the last iteration, set the image representing the selected number
img1.setAttribute("src", dieImage(selectedNum));
} else {
// else decrement the counter, set a random image and repeat after a timeout
counter--;
img1.setAttribute("src", dieImage(Math.floor(6 * Math.random()) + 1));
setTimeout(repeat, 300);
}
}
repeat();
}

Slowing down 2 nested loops

I'm trying to build one of my first web apps that would visualize sorting algorithms. I'm currently working on a bubble sort algorithm but I'm having trouble slowing the bubble sort loops execution down.
I tried using setTimeout() function but apparently, it is not that easy to use it on nested loops.
The piece of code below works well but if I add setTimeout within the nested loop then it only does the first iteration of the outer while loop. It is understandable as an entire while loop is executed instantly and isn't really aware that someone has desynchronized its' body execution.
I tried putting the outer loop in a setTimeout function but I couldn't find the way to retrieve the actual time that it is supposed to be delayed by as I tried using j value to calculate it but it is only available within the for loop.
Any idea on how to slow down execution of both loops in a relevant way?
bubbleSort(){
var len = this.numberOfElements;
var sorted = false;
var i = 0;
while(!sorted){
sorted = true;
for(let j = 0 ; j < len - i - 1; j++){
setTimeout( () => {
if (this.valuesAndPillarsObject[j].value > this.valuesAndPillarsObject[j + 1].value) {
//swap graphical representation of the value
this.swapPillars(this.valuesAndPillarsObject[j].pillar, this.valuesAndPillarsObject[j+1].pillar);
// swap values
var temp = this.valuesAndPillarsObject[j].value;
this.valuesAndPillarsObject[j].value = this.valuesAndPillarsObject[j+1].value;
this.valuesAndPillarsObject[j + 1].value = temp;
sorted = false;
}
}, j*40);
}
i++;
}
return this.valuesAndPillarsObject;
}
You don't want to just schedule a bunch of timeouts in a loop. They'll all finish after just one delay. You want to have each timeout start the next one. To keep using a loop to control the flow, you'll need to make use of async and await.
Wrong way
for (let i = 0; i < 5; ++i) {
setTimeout(() => { console.log(`step ${i}`) }, 1000);
}
console.log('finished');
Right way
const delay = time_ms => new Promise(resolve => setTimeout(resolve, time_ms));
const main = async () => {
for (let i = 0; i < 5; ++i) {
await delay(1000);
console.log(`step ${i}`);
}
}
main().then(() => console.log('finished'));
The for loop will finish before the first setTimeout callback is called !
even if the timeout is 1ms or 0ms.
read some about the EventLoop to understand how things works in JavaScript.
UPDATE: the simplest solution is using delay inside the for loop.
just like how Wyck did.
but here i will keep the other solutions
You can use function and call it again to demonstrate a loop instead of normal for loop.
// change the code as your needs
const sleep = (t) => ({ then: (r) => setTimeout(r, t) })
const len =10
// change slowFor for your needs
const slowFor= async (i,j)=>{
// do what you want here
await sleep(5000)
if(j == len) return ;
console.log("after sleeping call next loop");
slowFor(i,j+1)
}
slowFor(0,0)
be aware of maximum call stack when running on big arrays.
or you can convert the bubbleSort function to a generator function so you will be able to stop it whenever and as much time you want.
change the code to fit your need
function* bubble() {
for(let i = 1 ; i<10 ; i++ ){
for(let j=0 ; j<i ; j++, yield){
console.log("hellow from for");
}
}
}
const genBubble = bubble()
genBubble.next()
setInterval(() => {
genBubble.next()
}, 5000);

setTimeout in memory game

I am making a memory game in javascript that I am putting on my web page. I have 3 buttons labeled 1, 2, and 3, and they are supposed to turn green then white again in a certain order. The order is random and increases by 1 every turn. The player is then supposed to click the buttons in the correct order. The problem is that when I change the button color to green, instead of each button turning green and then white again in the correct order, the buttons turn green all at once. This is the javascript logic:
function lightUp() {
var arrayOrder = gameOrder.split("");
for(let i = 0; i < arrayOrder.length; i++) {
document.getElementById("but" + arrayOrder[i]).style.backgroundColor = "green";
setTimeout(function() {
document.getElementById("but" + arrayOrder[i]).style.backgroundColor = "white";
}, 1000);
}
}
gameOrder is a string of the number order (e.g. "12331232123")
I think the game isn't working because of the way setTimeout works, I believe it pauses in the background and allows the for loop to keep running instead of pausing the whole function for a second (which is what I am trying to do).
I want each button to turn on then off before the next button changes color. So if the order is 1 2 3, I want button 1 to turn green and then white then button 2 to turn green then white and finally for button 3 to turn green then white.
As espacarello has pointed out, code you run with setTimeout is not synchronous. You could make the function asynchronous and await the setTimeout.
This is my preferred way to "animate" things with JS:
async function lightUp() {
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
for (const i of gameOrder.split('')) {
const el = document.getElementById(`but${i}`);
el.style.backgroundColor = 'green';
await sleep(1000);
el.style.backgroundColor = 'white';
}
}
This stays simple and elegant without messying your code with numerous callbacks, recursion, or nested setTimeouts. It stays as one function.
Edit: It is important to note that IE does not support Promises or the Arrow Function syntax. See the other two answers for wider support.
Problem with your code is the for loop runs all at once without a delay. You code it thinking that the interval will cause the loop to wait until it is executed. Problem with that is, will just run without waiting.
Use a queue type of system where you run the step and when it is done, you run the next until you run out of things to do.
var steps = [1,2,3,2,1,3,1]
var delay = 1000
var step = 0
function next () {
var button = document.getElementById('btn' + steps[step])
button.classList.add("on")
window.setTimeout( function () {
button.classList.remove("on")
step++
if(step<steps.length) next()
}, delay)
}
next()
.on {
background-color: green;
}
<button id="btn1">One</button>
<button id="btn2">Two</button>
<button id="btn3">Three</button>
you can do it as below...
function lightUp() {
var arrayOrder = gameOrder.split("");
for (let i = 0; i < arrayOrder.length; i++) {
(function(i) {
setTimeout(function() {
document.getElementById("but" + arrayOrder[i]).style.backgroundColor = "green";
}, 1000 * i - 1000);
setTimeout(function() {
document.getElementById("but" + arrayOrder[i]).style.backgroundColor = "white";
}, 1000 * i); // schedules excution increasingly for each iteration
})(i);
}
}
to learn more about how it works please refere to this answer

Using setTimeout to update progress bar when looping over multiple variables

Suppose you have 3 arrays you want to loop over, with lengths x, y, and z, and for each loop, you want to update a progress bar. For example:
function run() {
x = 100;
y = 100;
z = 10;
count = 0;
for (i=0; i<x; i++) {
//some code
for (j=0; j<y; j++) {
// some code
for (k=0; k<z; k++) {
//some code
$("#progressbar").reportprogress(100*++count/(x*y*z));
}
}
}
}
However, in this example, the progress bar doesn't update until the function completes. Therefore, I believe I need to use setTimeout to make the progress bar update while the function runs, although I'm not sure how to do that when you have nested for loops.
Do I need to break each loop up into its own function, or can I leave them as nested for loops?
I created a jsfiddle page in case you'd like to run the current function: http://jsfiddle.net/jrenfree/6V4Xp/
Thanks!
TL;DR: Use CPS: http://jsfiddle.net/christophercurrie/DHqeR/
The problem with the code in the accepted answer (as of Jun 26 '12) is that it creates a queue of timeout events that don't fire until the triple loop has already exited. You're not actually seeing the progress bar update in real-time, but seeing a late report of what the values of the variables were at the time they were captured in the inner closure.
I'd expect that your 'recursive' solution looks a bit like using continuation-passing style to ensure that your loop doesn't continue until after you've yielded control via setTimeout. You might not know you were using CPS, but if you're using setTimeout to implement a loop, you're probably pretty close to it.
I've spelled out this approach for future reference, because it's useful to know, and the resulting demo performs better than the ones presented. With triple nested loops it looks a bit convoluted, so it may be overkill for your use case, but can be useful in other applications.
(function($){
function run() {
var x = 100,
y = 100,
z = 10,
count = 0;
/*
This helper function implements a for loop using CPS. 'c' is
the continuation that the loop runs after completion. Each
'body' function must take a continuation parameter that it
runs after doing its work; failure to run the continuation
will prevent the loop from completing.
*/
function foreach(init, max, body, c) {
doLoop(init);
function doLoop(i) {
if (i < max) {
body(function(){doLoop(i+1);});
}
else {
c();
}
}
}
/*
Note that each loop body has is own continuation parameter (named 'cx',
'cy', and 'cz', for clarity). Each loop passes the continuation of the
outer loop as the termination continuation for the inner loop.
*/
foreach(0, x, function(cx) {
foreach(0, y, function(cy) {
foreach(0, z, function(cz) {
count += 1;
$('#progressbar').reportprogress((100*(count))/(x*y*z));
if (count * 100 % (x*y*z) === 0) {
/*
This is where the magic happens. It yields
control to the javascript event loop, which calls
the "next step of the foreach" continuation after
allowing UI updates. This is only done every 100
iterations because setTimeout can actually take a lot
longer than the specified 1 ms. Tune the iterations
for your specific use case.
*/
setTimeout(cz, 1);
} else {
cz();
}
}, cy);
}, cx);
}, function () {});
}
$('#start').click(run);
})(jQuery);
You can see on jsFiddle that this version updates quite smoothly.
If you want to use setTimeout you could capture the x, y, z and count variables into a closure:
function run() {
var x = 100,
y = 100,
z = 10,
count = 0;
for (var i=0; i<x; i++) {
for (var j=0; j<y; j++) {
for (var k=0; k<z; k++) {
(function(x, y, z, count) {
window.setTimeout(function() {
$('#progressbar').reportprogress((100*count)/(x*y*z));
}, 100);
})(x, y, z, ++count);
}
}
}
}
Live demo.
Probably a jquery function in reportprogress plugin uses a setTimeout. For example if you use setTimeout and make it run after 0 milliseconds it doesn't mean that this will be run immediately. The script will be executed when no other javascript is executed.
Here you can see that i try to log count when its equal to 0. If i do it in setTimeout callback function then that is executed after all cycles and you will get 100000 no 0. This explains why progress-bar shows only 100%. js Fiddle link to this script
function run() {
x = 100;
y = 100;
z = 10;
count = 0;
for (i=0; i<x; i++) {
//some code
for (j=0; j<y; j++) {
// some code
for (k=0; k<z; k++) {
//some code
if(count===0) {
console.log('log emidiatelly ' + count);
setTimeout(function(){
console.log('log delayed ' + count);
},0);
}
count++;
}
}
}
}
console.log('started');
run();
console.log('finished');
wrapping everything after for(i) in setTimeout callback function made the progress-bar work. js Fiddle link
Edit:
Just checked that style setting code for item is actually executed all the time. I think that it might be a browser priority to execute javascript first and then display CSS changes.
I wrote a another example where i replaced first for loop with a setInterval function. It's a bit wrong to use it like this but maybe you can solve this with this hack.
var i=0;
var interval_i = setInterval(function (){
for (j=0; j<y; j++) {
for (k=0; k<z; k++) {
$("#progressbar").reportprogress(100*++count/(x*y*z));
}
}
i++;
if((i<x)===false) {
clearInterval(interval_i);
}
},0);
JS Fiddle
I've found a solution based on the last reply but changing the interval time to one. This solution show a loader while the main thread is doing an intensive task.
Define this function:
loading = function( runme ) {
$('div.loader').show();
var interval = window.setInterval( function() {
runme.call();
$('div.loader').hide();
window.clearInterval(interval);
}, 1 );
};
And call it like this:
loading( function() {
// This take long time...
data.sortColsByLabel(!data.cols.sort.asc);
data.paint(obj);
});

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

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
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?
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.
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);
}
This's Because!
The timeout function
callbacks are all running well after the completion of the loop. In fact,
as timers go, even if it was setTimeout(.., 0) on each iteration, all
those function callbacks would still run strictly after the completion
of the loop, that's why 3 was reflected!
all two of those functions, though they are defined
separately in each loop iteration, are closed over the same shared global
scope, which has, in fact, only one i in it.
the Solution's declaring a single scope for each iteration by using a self-function executed(anonymous one or better IIFE) and having a copy of i in it, like this:
for (var i = 1; i <= 2; i++) {
(function(){
var j = i;
setTimeout(function() { console.log(j) }, 100);
})();
}
the cleaner one would be
for (var i = 1; i <= 2; i++) {
(function(i){
setTimeout(function() { console.log(i) }, 100);
})(i);
}
The use of an IIFE(self-executed function) inside each iteration created a new scope for each
iteration, which gave our timeout function callbacks the opportunity
to close over a new scope for each iteration, one which had a variable
with the right per-iteration value in it for us to access.
The function argument to setTimeout is closing over the loop variable. The loop finishes before the first timeout and displays the current value of i, which is 3.
Because JavaScript variables only have function scope, the solution is to pass the loop variable to a function that sets the timeout. You can declare and call such a function like this:
for (var i = 1; i <= 2; i++) {
(function (x) {
setTimeout(function () { alert(x); }, 100);
})(i);
}
You can use the extra arguments to setTimeout to pass parameters to the callback function.
for (var i = 1; i <= 2; i++) {
setTimeout(function(j) { alert(j) }, 100, i);
}
Note: This doesn't work on IE9 and below browsers.
ANSWER?
I'm using it for an animation for adding items to a cart - a cart icon floats to the cart area from the product "add" button, when clicked:
function addCartItem(opts) {
for (var i=0; i<opts.qty; i++) {
setTimeout(function() {
console.log('ADDED ONE!');
}, 1000*i);
}
};
NOTE the duration is in unit times n epocs.
So starting at the the click moment, the animations start epoc (of EACH animation) is the product of each one-second-unit multiplied by the number of items.
epoc: https://en.wikipedia.org/wiki/Epoch_(reference_date)
Hope this helps!
You could use bind method
for (var i = 1, j = 1; i <= 3; i++, j++) {
setTimeout(function() {
alert(this);
}.bind(i), j * 100);
}
Well, another working solution based on Cody's answer but a little more general can be something like this:
function timedAlert(msg, timing){
setTimeout(function(){
alert(msg);
}, timing);
}
function yourFunction(time, counter){
for (var i = 1; i <= counter; i++) {
var msg = i, timing = i * time * 1000; //this is in seconds
timedAlert (msg, timing);
};
}
yourFunction(timeInSeconds, counter); // well here are the values of your choice.
I had the same problem once this is how I solved it.
Suppose I want 12 delays with an interval of 2 secs
function animate(i){
myVar=setTimeout(function(){
alert(i);
if(i==12){
clearTimeout(myVar);
return;
}
animate(i+1)
},2000)
}
var i=1; //i is the start point 1 to 12 that is
animate(i); //1,2,3,4..12 will be alerted with 2 sec delay
the real solution is here, but you need to be familiar with PHP programing language.
you must mix PHP and JAVASCRIPT orders in order to reach to your purpose.
pay attention to this :
<?php
for($i=1;$i<=3;$i++){
echo "<script language='javascript' >
setTimeout(function(){alert('".$i."');},3000);
</script>";
}
?>
It exactly does what you want, but be careful about how to make ralation between
PHP variables and JAVASCRIPT ones.

Categories