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.
Related
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.
Problem is that setTimeout function within for loop is not getting the correct values despite the fact they were passed correctly. Second problem is that the sleep variable is not working, it simply ignores it. Other variables like i are acting strange, they are not going though the loop, they are going in a random order.
Inside function set_delay
console.log(i);
// 3,5,0,2,4,1 should be 0,1,2,3,4,5
console.log(sleep);
// 6000,6000,7000,9000,9000,10000, those ones are displayied in ASC order but shuold be randomly
console.log(share_screen_rate[i]);
//4,1,6,10,6,2,8 - this is not ok it's random
console.log(top);
// 749.5,2998,299.8,499.667,149,374.75 => this should be in order from smallest (149) to biggest (2998)
setTimeout(function() {
}, sleep);
if I change sleep to a numeric value like 2000 it is respected only for first iteration after nothing, it just goes at 0ms.
FULL CODE
https://jsfiddle.net/ojpv2nxu/
EXPECTED OUTPUT
This should be a simple script to scroll down the page and make pause based on the sleep variable and also read the page chunk by chunk based on the share_screen_rate variable
I think it is a logic issue.... You are expecting the setTimeout to happen x seconds after the last one. But they are firing x seconds after you set it. So it is working as expected.
If you want them to fire x seconds after the last, than you need to change the logic to keep track of the seconds and adjust the timers.
so top of the file add
var timeTrack = 0
and than you need to add to that value
timeTrack += sleep
setTimeout(function() {
}, timeTrack);
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();
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.
I know at first glance (due to the title) this looks like one of the "Did you try searching Google before posting?" questions, but I can't seem to find an answer for the specific issue I'm experiencing. Sorry if I'm a noob.... still learning :)
I need to simulate a pause in javascript, but the setTimeout(function_call, timeout) function is not working for me. Reason being... when that setTimeout function is called, it then makes the function call asynchronously.
A little background:
I'm trying to simulate text being typed into a div at randomly timed intervals. I want it to appear as if a person is actually typing a message while the user is viewing the page. Since the timeout is a random interval and the function call is made asynchronously, the text ends up being printed in a random order.
Here is a snippet of what I have thus far:
typeString: function(s)
{
for(var i=0;i<s.length;i++)
{
var c = s.charAt(i);
var temp = setTimeout("MessageType.typeChar('" + c + "')", this.genRandomTime());
}
}
Thanks in advance for your help.
CJAM
UPDATE: By adding the timer delay to a varialbe, it enabled me to offset timeOut for the asynchronous calls. Thank you all for your quick responses. Here is the updated code:
typeString: function(s)
{
var delay = 0;
for(var i=0;i<s.length;i++)
{
var c = s.charAt(i);
setTimeout("GoogleTyper.typeChar('"+c+"')", delay += this.genRandomTime());
}
}
Have you tried cumulatively setting the timeout? Stick a variable outside of your loop and initialize it to 0. Let's call it timeout for now. Add a random amount of time to this variable at the beginning of each iteration of your loop, and make the timeout work off of that. This way, you are ensured that functions are being called in order.
Your problem is you are using one all your times are delayed starting from now - the next timer needs to be fired after the previous. Simply add the previous timer delay to the new timer.
typeString: function(s)
{
var delay = 0;
for(var i=0;i<s.length;i++)
{
var c = s.charAt(i);
delay = delay + this.genRandomTime();
var temp = setTimeout("MessageType.typeChar('" + c + "')", delay );
}
}
Instead of passing to each timer event that character to be displayed, let each event handler grab the next character off of a queue (array, list whatever), so the order is maintained.