do while loop with setTimeout causing infinite loop - javascript

I'm working on a simon game(the one you follow the color pattern). It makes it through the computers 1st turn and my 1st turn but trying to do a setTimeout between each computer choice is causing an infinite loop with the do while statement or playing both choices at the same time if I use for loop. The highlightDiv function just does a toggleClass on the div, then a setTimeout to toggle the class back off. And the audioStart function uses a switch statement to determine which sound to play and then a setTimeout of half a second to play that sound. I thought this setTimeout on the increment would allow enough time for those two things to happen before it incremented and then did the next index in the computerChoice array. This is the codepen if that is easier: http://codepen.io/RawleJuglal/pen/pgRVKd
var computerChoice = ["red", "yellow"],index=0;
function computerPattern(cPattern, index){
console.log("Entered computer pattern function");
console.log("This is cPattern");
console.log(cPattern);
console.log("This is index: "+ index);
if(index !== cPattern.length)
{
highlightDiv(cPattern[index]);
audioStart(cPattern[index]);
index++;
computerPattern(cPattern, index);
}
else
{
index=0;
}
console.log("Leaving computerPattern function");
}
computerPattern(computerChoice, index);

Javascript is single threaded, and the concept of timeouts means that you place a function on a special queue, which is to execute your callbacks when their time is due. now, since in your code, the i variable is being updated only in the timeout function, which is only after 3 seconds, it means that the body of the loop will run again and again until 3 seconds are met.
in 3 seconds, javascript can run thousands of iterations, and each iteration registers another timeout, which means that your event queue is blasted and your single thread will have hard time completing all those tasks until i is finally reaching cPattern.length, if ever.
your solution might be using some sort of setInterval which has a callback that does what you want, and stops on some iteration variable that increments every time, let's say like this:
var interval = setInterval(function(){
console.log(cPattern[i]);
highlightDiv(cPattern[i]);
audioStart(cPattern[i]);
i++;
if(i >= cPattern.length){
clearInterval(interval);
}
},
2000);

Your timeout function is never called because you don't give it a chance. As long as your code is running (your loop), the browser can't run the scheduled scripts, which use the same thread. You will have to rethink the code.

You're incrementing a variable called i within the anonymous function delegate that you're passing to setTimeout. The locally-scoped variable i on which the do-while loop depends is never updated.
Depending on the functionality you're looking for, you can increment the variable i while it's still in scope, then use a closure to pass a snapshot of its value to the setTimeout function delegate.
function run(){
var i = 0
do{
(function(i){
setTimeout(function(){
alert(i);
},1000);
})(i++)
}
while(i < 2);
}
run();

Related

How does this interesting piece of JavaScript code work?

A friend of mine brought to my attention a piece of JavaScript code that gives you unrealistic time scores on the flashcard website Quizlet's match game. It somehow stops the game's timer at the user specified time.
document.getElementsByClassName("UIButton UIButton--hero")[0].click();
setTimeout(function(){for(var F = setTimeout(";"), i = 0; i < F; i++) clearTimeout(i)}, 5100); //Change 5100
Using it is simple, you get on the match game (for example https://quizlet.com/187478162/match) and you simply enter this code in the console in the inspect menu. You then need to complete the game but it doesn't matter how long you take.
I don't know JavaScript (I am very knowledgeable with Python) but I have figured out so far that the first line clicks the start button and the first setTimeout function waits until the specified time to execute the function inside of it. It is the function inside that confuses me. It seems to just create and clear a bunch of Timeouts. I have no Idea how this stops the game timer though.
If someone could explain how it works that would be very much appreciated
Ah, that's a very interesting piece of code, very clever. Here's what's happening.
The inner function basically has the following (expanded for readability):
var F = setTimeout(";")
for(i = 0; i < F; i++) clearTimeout(i)
setTimeout can accept functions or strings that get evaluated after a timeout. They're just passing in the no-op ';', and they're not even passing in a delay. What they care about is the timer-id "F". If you go to the dev-tools (on a non-busy page), and put setTimeout(';') in the console, you'll see that it'll first return the id 0, then 1, then 2, and so on. The ids counts up.
So, you can imagine this timeline:
Random background stuff happens
The webpage starts a timeout with setTimeout, that has, lets say, id 7.
More background stuff
This script executes. It creates a timeout, and gets back an id that's going to be bigger than any timeouts made in the past. Lets say that id is 9.
Now the script goes through all ids from 0 to 9, stopping any active timeouts by passing in the id to clearTimeout. This includes clearing the timeout for id 7.
Before I get to the answer part, you should know a couple of things about setTimeout( ):
The setTimeout( ) function, can accept a callback function as it's first parameter, or, it can also accept code in a 'string'.
Whenever the setTimeout( ) function is called, it returns a timeout id which can be used in order to pass into the clearTimeout( ) function when you want to clear a particular timeout.
Now, please run the below code snippet and see what happens:
a) In the very first setTimeout call, I'm passing a callback function, storing the returned id in the const variable 'id1'.
b) In the second setTimeout call, I'm passing a code string, storing the returned id in a const variable 'id2'.
const id1 = setTimeout(() => console.log('setTimeout with callback fn'), 1000, '_');
const id2 = setTimeout('console.log("setTimeout with code string")', 2000);
console.log('id from 1st setTimeout( ) is :', id1);
console.log('id from 2nd setTimeout( ) is :', id2);
Both the setTimeout calls work without an issue, but the more interesting thing is when you look at the 'id' values for both function, for the first call the id is '1', for the second call the id is '2'.
This means two things: One these IDs are unique and secondly, these IDs are in the same ID pool, think of it like an ID list or an array where for each setTimeout call, a new ID is created by incrementing over the last existing setTimeout ID from the ID Pool.
Now, let's look at the code in question:
In the first line of code, the getElementsByClassName returns a nodelist of all the elements with the class passed in, and with the click( ), you are just simulating a buttonclick for the 0th element from the nodelist that is returned. You can use other functions like querySelector('UIButton UIButton--hero') or querySelecorAll('UIButton UIButton--hero') in this case and it will not make a difference in the way this works.
document.getElementsByClassName("UIButton UIButton--hero")[0].click();
Now, when we look at the below setTimeout( ) function calls with the knowledge we have about how the setTimeout function and setTimeout IDs work, it will be way easier to understand how it stops the actual timer:
setTimeout(function () {
for (var F = setTimeout("console.log(';')"), i = 0; i < F; i++)
clearTimeout(i);
}, 5100); //Change 5100
We are making an outer setTimeout call which takes in a callback function, and the timer for this setTimeout is set to '5100' milliseconds, which is basically a 5 second timer.
Looking at the callback function itself:
function () {
for (var F = setTimeout("console.log(';')"), i = 0; i < F; i++)
clearTimeout(i);
}
This callback function, runs a for loop, in the initialization block of this for loop, two variables are initialized, 'F' and 'i', value of 'F' is the ID returned by the current setTimeout( ) function, 'i' is set to 0.
Now from what we know from the code snippet I posted above is that the ID pool for all the IDs is the same, which means the ID 'F' is definitely going to be greater the setTimeout ID of the original timer that is present on the webpage.
Answer & Conclusion:
Now the loop itself iterates basing on the value of 'i', as long as it is less than 'F', and it clears the interval using clearInterval(i) for each value of i from 0 to F, which means it stops all timers on the page from ID = 0 to ID = 'F'.
And that is the reason this code can be used in order to exploit the website and stop it's timer so users can cheat and complete the quiz. Please let me know if you have any further queries.

For loop, Let, and setTimeout

for (let i=1; i<=5; i++) {
setTimeout( function timer(){
console.log( i );
}, i*1000 );
}
Biggest thing I'm confused about here - Why does this print 1,2,3,4,5 - All one second apart? Rather than incrementing one second, and taking one second longer each time we go through the for loop? I also have another variation that confuses me, where I do a console.log(i) as the last step in the for loop, and it just instantly posts 1-5. Any help here?
Coming from C#, for the record, not sure if the behaviour exists in C# or if I'm just a bit rusty.
Thanks!
It is actually quite simple. The for loop schedules the timer function for all five values. Then the timer function starts printing the numbers. Now the reason for them being printed 1 second apart is i*1000 in setTimeout. As a result, 1 will be printed 1 second after it has been scheduled, 2 will be printed 2 seconds after it has been scheduled, and approximately 1 second after 1 has been scheduled, and so on...
See the snippet below to understand how it works. Keep in mind that setTimeout does not block the execution of the for loop.
for (let i=1; i<=5; i++) {
setTimeout( function timer(){
console.log( i );
}, i*1000 );
console.log("Print " + i + " after " + i +" seconds.");
}
console.log("for loop completed.");
setTimeout() will place your timer() into the queue.
for loop execute very quickly nearly immediately, each loop timer() will be placed into the queue with delays: 1,2,3,4,5 seconds respectively.
console.log(i) will print 1-5 because of the immediate execution of for loop.
I guess what the other answers have not touched upon is the event loop.
The event loop is a distinct execution thread than the main JS thread.
Your for loop would execute in the main JS thread, pushing each timeout on the event loop which would be executed in the event loop as scheduled. The time scale of events would look something like this.
I hope this is what you are looking for
System.Threading.Thread.Sleep(1000*i);

For Loop Iteration Happening all at Once Rather than By Delay

For one of my elements on my page, I want the text to change every ten seconds, and for the class to be changed. Text changing is easy, as is class changing, but I'm having trouble with my for loop, and I feel like I'm missing something.
I want to have the for loop choose a random faction in an array, and then apply that to the element. For my testing, I've been using console.log rather than DOM manipulation.
First, I set up my array:
var factions = ["Enforcers", "Reapers", "Ular Boys", "Roaches"];
Then, I want a variable that is a number chosen at random in reference to this array:
var x = factions[Math.floor(Math.random()*factions.length)];
From that, I want the ability to run the Math.floor and Math.random functions elsewhere.
function reDefine() {
x = factions[Math.floor(Math.random()*factions.length)];
console.log(x);
}
Finally, I want the for loop to run 200 times (I've chosen 200 times because it's far and beyond the time the user will be staying on the site), so I told it to count to 200 (i = 0; i < 200). After that, I wanted each time it iterated, to wait 10s, so I have a Timeout function with a delay of 10000 (milliseconds). Then, the code to reDefine and then, in the case of testing, console.log the new definition of the x variable.
function reChange() {
for (var i = 0; i < 200; i++) {
setTimeout(function() {
reDefine();
console.log("Chosen faction is now: " + x);
}, 10000);
}
}
Instead of counting to 1 (the first iteration), waiting 10000, and then redefining x, it redefines x two hundred times, then logs them all.
Is there something I'm specifically doing wrong here, perhaps with the Timeout function?
Is there something I'm specifically doing wrong here, perhaps with the Timeout function?
Yes! You're scheduling a bunch of deferred callbacks, but not actually waiting until one has finished to schedule the next.
You can fix that with something as simple as:
function reChange(currentIndex) {
setTimeout(function() {
reDefine();
console.log("Chosen faction is now: " + factions[currentIndex]);
// If we haven't gotten to the end of the list, queue up another one
var nextIndex = ++currentIndex;
if (nextIndex < factions.length) {
// Enqueue the next faction
reChange(nextIndex);
}
}, 10000);
}
Make sure to note that the function without the timeout has closure over the value of currentIndex for each call of reChange. That is, the next invocation does not replace currentIndex in any previous timeout, since primitives (including numbers) are passed by value. Closure in JS can be a tricky thing.
The core problem is that your execution right now looks like:
for each item
wait
log
rather than:
for the current item
wait
log
repeat
Because JS is single-threaded (for most intents and purposes), setTimeout adds a callback to be executed later. It doesn't block until the timeout has expired, like a traditional sleep would do.

understanding setInterval in javascript

I have a function which does something async like saving to database. Want a mechanism that first inserts the row and the next insertion should occur only when the first insert operation has finished.
Here is what I have tried and it somewhat works.
var interval = true;
function insert() {
model.save(function () {
interval = true;
})
}
foreach(row, function (key, val) {
var interval1 = setInterval(function () {
if (interval) {
insert();
interval = false;
clearInterval(interval1);
}
}, 100)
})
Is it the correct approach of doing this? Please shed some light about my understanding of timers in javascript.
No, you should not be creating timers to poll for when something is done. That's probably the worst way you can do it. What you want to do is to explicitly start the next iteration each time the previous one finishes.
Here's the general idea for how you do this without polling. The idea is that you need to create a function that can be called successive times and each time it's called, it will perform the next iteration. You can then call that function from the completion handler of your async operation. Since you don't have a nice convenient foreach loop to control the iteration, you then have to figure out what state variables you need to keep track of to guide each iteration. If your data is an array, all you need is the index into the array.
function insertAll(rows) {
// I'm assuming rows is an array of row items
// index to keep track of where we are in the iteration
var rowIndex = 0;
function insert() {
// keep going as long as we have more rows to process
if (rowIndex < rows.length) {
// get rows[rowIndex] data and do whatever you need to do with it
// increment our rowIndex counter for the next iteration
++rowIndex;
// save and when done, call the next insert
model.save(insert)
}
}
// start the first iteration
insert();
}
If you don't have your data in an array that is easy to step through one at a time this way, then you can either fetch each next iteration of the data when needed (stopping when there is no more data) or you can collect all the data into an array before you start the operation and use the collected array.
No, this is absolutely not the right way to do this. Lets assume that row contains 10 values, then you are creating 10 independent timers which continuously run and check whether they can insert. And it's not even guaranteed that they are executed in the order they are created.
As jfriend00 already mentioned, you should omit the "loop" and make use of the completion callback of the save operation. Something like this:
var rows = [...];
function insert(rows, index) {
index = index || 0;
var current_element = rows[index];
model.save(function() {
if (index < rows.length - 1) {
insert(rows, index + 1);
}
});
}
insert(rows);
Notice how the function calls itself (somehow) after the save operation is complete, increasing the index so the next element in the array is "saved".
I would use a library that handles async stuff such as async.js
BTW it seems like your model.save methods takes a callback, which you can use directly to call the insert method. And if the insert function is one you have made by yourself, and not a part of some bigger framework, I will suggest to re-write it and make take a callback as parameter, and use that instead of using setInterval for checking when async work is done.

setInterval with an Array

I'd like to use the setInterval function in jQuery in order to create an alert with the content of one array every 4 seconds. However my alerts show all the values of my array within a short amount of time and it stops for 4 seconds after having displayed all the values.
$.each(['html5', 'EDM', 'Coca Cola', 'creativity'], function(id,value) {
setInterval(function(){
alert(value);
}, 4000);
});
In this case, I'd like to display something like : Alert('html5') - 4 seconds - Alert('EDM') - 4 seconds - Alert('Coca Cola') - 4 seconds - Alert('creativity') - 4 seconds - Alert('html5') - 4 seconds - Alert('EDM') - 4 seconds - ...
Move the setInterval from the loop.
var arr = ['html5', 'EDM', 'Coca Cola', 'creativity'];
var index = 0;
setInterval(function() {
console.log(arr[index++ % arr.length]);
}, 4000);​
Live DEMO
No jQuery needed.
Use a recursive setTimeout
var arr = ['html5', 'EDM', 'Coca Cola', 'creativity'];
var alertLoop = function(i) {
if (arr[i]) {
alert(arr[i]);
setTimeout(function(){alertLoop(i+1);}, 4000);
}
}
alertLoop(0);
Demo: http://jsfiddle.net/B5tJw/
Use of setInterval is discouraged. For an explanation, read here: http://bonsaiden.github.com/JavaScript-Garden/#other.timeouts
To summarise the problem:
setInterval fires the event at a regular interval, regardless of what else is happening on the page.
However, Javascript is not multi-threaded: it can only run one code sequence at a time. If setInterval is triggered while another code sequence is being run, the new code sequence will be blocked until the previous one is finished, and will wait for it.
If this happens repeatedly, you can end up with a large number of events waiting to be run.
You're using alert() to display your messages. alert() causes the code execution sequence to pause until the user responds to the message, but keeps it locked, so that other events cannot run their code. This is a particular problem for setInterval, because it fires new events at the specified time, regardless of whether something else is blocking the script.
The solution is to use setTimeout instead of setInterval.
setTimeout is only triggered once, but it is easy to tell it to trigger itself again inside its own function, so you can get the same effect as setInterval, but with much more control. Your code can wait until after the alert() has been accepted by the user before triggering the next event, which means that you won't get the problem of cascading events that can happen with setInterval.
Hope that helps explain things. The link I mentioned at the beginning is also very helpful.

Categories

Resources