Sequentially firing multiple random timeouts in JavaScript - javascript

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.

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.

how to download multiple audio files using download attribute?

i was wondering if i can download all audio files from google word pronunciation
i came up with a solution that actually works
i created an html page with the following code
<a id="mylink" href='//ssl.gstatic.com/dictionary/static/sounds/de/0/word.mp3' download='word.mp3'>download audio "word"</a>
now if you click the above link , the browser will start downloading the audio file
and if you change word.mp3 to something else and click it again ,
it will start downloading the new word;
the problem is I tried to download the whole dictionary in mp3 format.
so i came up with the following javascript
var words=['cat','fish','police','office','ball'];//the whole dictioany.....
for(var z in words )
setTimeout(function(){
var lin=document.querySelector("a");//gets the above link
lin.href="//ssl.gstatic.com/dictionary/static/sounds/de/0/"+a[z]+".mp3";//changes link href to each of the array "words"
lin.download=a[z]+".mp3";//changes the file name to current word
lin.click();//after clicking this link , all downloads start at the same time
},1000)//even it setup to fire after 1 second
//all the downloads start at same time
the biggest problem is that only audio file downloaded is the last one "ball"
and it's downloaded multiple times
any solution is appreciated and thanks in advance
here is the example on jsfiddle click
You can't use a setTimeout() in a loop, as the loop will execute faster than the timeout. Thus, the final result will be ran for each event in the loop (a[z] will call a[4] five times). To get around this, you'll need to slightly modify your for loop, and not use a timeout:
var a = ['cat', 'fish', 'police', 'office', 'ball'];
for (var z = 0; z < a.length; z++) {
var l = document.querySelector("a");
l.href = "//ssl.gstatic.com/dictionary/static/sounds/de/0/" + a[z] + ".mp3";
l.download = a[z] + ".mp3";
l.click();
}
I've created an updated fiddle showcasing this here.
Hope this helps!
The problems with your code are:
The setTimeout is executed within the loop at nearly the same time. So that is why the function is also executed at the same time. Think of having ten egg timers that you all activate at the same time. They will also all ring at the same time.
The function triggered by setTmeout uses the variable z declared outside the function. It does not use the value of z at the time when setTimeout is executed, but at the time the function is executed. All instances of the function access the same variable, so they all use the value of z during the last loop.
Instead of having a loop, use setInterval to execute a function at a given interval and use a counter variable so that a different variable is used at each time the function is executed.
function run()
{
var words=['cat','fish','police','office','ball'];
var counter = 0;
// Setup IntervalFunction to run every 2 seconds
var interval = setInterval(IntervalFunction, 2000);
function IntervalFunction()
{
// Download word
downloadWord(words[counter]);
// Increase counter
counter++;
// When counter is beyond array, stop IntervalFunction from running
if(counter >= words.length)
{
clearInterval(interval);
}
}
}
function downloadWord(word)
{
var lin = document.querySelector("a");
lin.href = "https://ssl.gstatic.com/dictionary/static/sounds/de/0/" + word + ".mp3";
lin.download = word + ".mp3";
lin.click();
}

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.

Javascript - passing index to setInterval callback

The problem
I'm trying to simplify a long javascript code and i have a problem with identifying callbacks.
I have a large array with elements to animate on page
[selector, activate interval, hide after]:
things_to_move = [
['.ufo, .chefoven, .sushi', 9900, 2000],
['.hotdog,.pizzaman,.boyballon', 12090, 3600],
(...)
]
Basically, the aim is to to activate each of the selectors every x seconds, and hide them x seconds later, as per the example above.
Current code
After many tries, I ended up with this:
// Activate the element, and set timeout to hide it
var showFun = function(index1) {
$(things_to_move[index1][0]).addClass('move');
setTimeout( function(){hideFun(index1)},things_to_move[index1][2]);
}
// Hide the element
var hideFun = function(index2) {
$(things_to_move[index2][0]).removeClass('move');
}
// Loop through all items and set the interval for each one
for(_A=0; _A < things_to_move.length; _A++) {
setInterval(function(){showFun(_A)}, things_to_move[_A][1]);
}
But of course this doesn't work. Every time the showFun function is called, it takes the value of _A after the loop finished and not the value at which setInterval was set.
Question
So the question is, how can i pass a unique index into the setInterval callback, so the callback knows which array item to use?
Final solution
If anyone is interested, the final solution: Fiddle
The most direct way to solve it is using closures.
Try something like this:
for(_A=0; _A < things_to_move.length; _A++) {
setInterval((function(_innerA){
return function(){ showFun(_innerA); };
})(_A), things_to_move[_A][1]);
}

Making a total number count appear to increase

Basically I have a class counting system that displays the number of classes and displays them in a span element. Below is the code:
$.get('other.html', function(data) {
$('#total').html($('.doc', data).length);
});
This works perfectly, however I'd like a way to have the numbers increasing one by one since the span element contains 0 when the page loads. Here's an example (the numbers increasing on here).
For this I have tried setTimeout and despite this not working anyway, I released it would simply delay the function and then display the end number. I have heard of periodical or something similar being used but could not find this in the example source code.
I am really sorry for more poor phrasing. If you have no idea what I mean then just ask and I'll try rephrase or find a better example.
The key is the function which increases the number should set a setTimeout to call itself, before termination. This way it will always be called again. If you want the option to stop the incrementing, you can add a global variable, and the function will only set a new timeout when that variable is true.
Example:
var doIncrement = true;
var numberToIncrement = 0;
function increment {
numberToIncrement++;
$('#mySpan').text(numberToIncrement);
if (doIncrement) {
setTimeout(increment, 1000);
}
}
You could use the setInterval function that allows you to run code at some time intervals. clearInterval allows to stop the task.
http://jsfiddle.net/d52Pw/
var $totalEl = $('#total');
$.get('other.html', function(data) {
var len = $('.doc', data).length,
count = 0,
int = setInterval(function () {
if (++count === len) {
//when we reach len, we stop the task
clearInterval(int);
}
$totalEl.html(count);
}, 500); //run every 1/2 sec
});

Categories

Resources