Simulating JavaScript's setTimeout() method “from first principles” - javascript

I'm trying to run setTimeout() on a website via Firefox's Scratchpad. It seems that there were various Firefox bugs regarding this particular JavaScript method - and it's twin, setInterval(). In Firefox v.56 and the latest Waterfox (both pre-"Quantum"), these JavaScript methods do not seem to work at all. By contrast, in the Firefox "Quantum" versions, it does seem to work... That is, the very same code works in FF "Quantum", which does not work in the immediate pre-"Quantum" versions of Firefox. And yes, I've tried all sorts of variations.
Circumstance has it that I'm stuck with the pre-Quantum version(s) of Firefox for this exercise and I need to find a way to "build" this setTimeout() method from first principles, as it were.
One thought would be to take the current date/time and then loop through a check to see if, say, 10 seconds (or 5 minutes) have passed, before continuing with further code/script execution.
Any ideas how to simulate setTimeout() resource-efficiently with JavaScript but without setTimeout() or setInterval() ?
---EDIT---
False alarm... could get setInterval() to work in the older browsers too; my bad!
(this means the reason for asking the question is gone, but the question as such may remain...)
---2nd EDIT---
Whereas setTimeout() works here:
setTimeout(function(){ alert("Hello"); }, 3000);
it does not work / seems to be ignored here:
i=0;
while(i < 100)
{
// window.open("https://www.cnn.com","_self");
// window.open("https://www.bbc.com","_self");
// setTimeout(function(){ alert("Hello"); }, 3000);
setTimeout(function(){ window.open("https://www.bbc.com","_self") }, 3000);
setTimeout(function(){ window.open("https://www.cnn.com","_self") }, 3000);
alert(i);
i++;
}
// security mgr vetoed ???
Why?

If you really-really want to simulate the setTimeout function without blocking the browser and so on, you can try use the requestAnimationFrame function to get some delay. It should work in the Firefox 14.0+. Something like that:
function mySetTimeout (callback, timeout) {
var startTime = performance.now();
function mySetTimeoutRec () {
requestAnimationFrame(function () {
// This way this function will be called
// asynchronously around 60 times per second
// (or less frequently on the inactive tabs).
// We can check that enough time has passed
// and call the user callback or this function again.
var currentTime = performance.now();
var elapsedTime = currentTime - startTime;
if (elapsedTime < timeout) {
mySetTimeoutRec();
} else {
callback();
}
});
}
mySetTimeoutRec();
}
It can be used just like setTimeout:
mySetTimeout(function () { console.log('Howdy.'); }, 10 * 1000);
Anyway you should not try to do something like that in the most cases. If it seems that you are have problems with setTimeout, that's probably something else is wrong.

One possibility would be to take advantage of a network request that takes a variable number of seconds to complete:
function artificialSetTimeout(fn, timeout) {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) fn();
}
xhr.open('get', 'https://httpstat.us/200?sleep=' + timeout);
xhr.send();
}
console.log('start');
artificialSetTimeout(() => console.log('end'), 2000);
Don't rely on its timing to be entirely accurate, though.

Related

Decreasing a variable by increments over time with setInterval

So right now, I am a beginner in coding and I'm having quite a few issues with the setInterval command. What I am trying to do is have a function that decreases a variable by 1 every time 5 seconds pass. However, although I have looked at many different threads with information about the setInterval command, none of them seem to fit my needs (although I may have missed something) and I have been unable to manipulate anything I have seen elsewhere to perform my function.
while (fullness <= 10) {
setInterval(hungry{ fullness--; }, 1000);
}
Why your code is wrong:
//There is no need to loop and call setInterval multiple times
//setInterval will fire the callback function every x milliseconds,
//In your example you have it firing every 1 second(1000 milliseconds)
//while (fullness <= 10) {
//setInterval(hungry{ fullness--; }, 1000);
//}
To fix this:
On page load (document.ready in jquery)
Do just one call to setInterval()
setInterval(function(){
if(fullness <= 10){
hungry{ fullness--; }; //Not sure about this line as it comes from your example
}
}, 5000); //Pass second parameter to setInterval of 5000 milliseconds (5 seconds wait)
You may be trying to use setInterval() in a synchronous way rather than asynchronously.
When you call setInterval(function, period) it only begins a timer that calls the given function every period milliseconds. After calling setInterval javascript will continue to execute the next lines of code right away. If you were to check for your fullness value right after the while loop ends, you might notice it hasn't changed (yet!).
I suggest that you write a new function to handle changing fullness and reacting to the change:
function updateHunger() {
fullness--;
if (fullness < 10) {
//Do something
}
else {
//You have to call clearInterval() to stop the timer
clearInterval(hungerTimer);
}
}
Then use setInterval like this:
//Using a variable to store the timer reference that we create
hungerTimer = setInterval(updateHunger, 5000);
Remember to declare the hungerTimer variable in a scope where it can be accessed from both updateHunger() and the method that calls setInterval().
You have to first set a variable for setInterval and then stop the iteration with clearInterval (important, otherwise the loop will iterate indefinitely). And check for fullness to be greater than 0.
var fullness = 10;
var timer = setInterval(function(){
if(fullness > 0){
fullness--;
}
else{
clearInterval(timer);
}
}, 5000);
Here is the working jsFiddle
The reason you are bumping into this is that JS runs in a single thread. Blocking it by waiting for 1 second would make the entire browser stall, which is why JS does not allow it and we do not have sleep() in JS, we have the Timers API.
But it's nonetheless nice to write straight up for-loops that look synchronous because that's how we "normally" think. That's why you can actually nowadays write something like this if using an engine with async and generator support:
// ES6 features
const sleeper = (x, timeout) =>
new Promise(resolve => setTimeout(resolve, timeout, x));
function* timer(count, timeout) {
for (let i = count; i >= 0; i--) {
yield sleeper(i, timeout);
}
}
async function main() {
for (let i of timer(10, 1000)) {
console.log(await i); // Blocks the main() function but not the JS main thread!
}
}
main();
console.log("The call to main() was non-blocking");

How can I rewrite alternative $setTimeout function such that it is easier to read?

Initially, in AngularJS, I had a function I wanted to periodically call. So, I used the $setInterval function to do this. However, upon doing so, I noticed that even when leaving the page, the function continued to run and completely filling my console with errors until I navigated back onto the page associated with the function. I replaced my $setInterval function, with a solution I found here. This guy writes a whole new version of $setInterval. It appears as:
function interval(func, wait, times) {
var interv = function(w,t){
return function(){
if(typeof t === "undefined" || t--> 0){
setTimeout(interv, w);
try{
func.call(null);
}catch(e){
t = 0;
throw e.toString();
}
}
};
}(wait,times);
setTimeout(interv,wait);
};
And I basically call my function as:
interval($scope.setValue, 100);
This function works exactly as I wish, and our prior problem has been solve. But now, readability has become an issue, and I have wondering if there was a way to rewrite this function such that it is easier to read (possible has less code) but functions in the same manner?
Here's a take on it, with comments for clarity which make it seem longer although it isn't:
function interval(func, wait, times) {
// Set `t` to either Infinity or the number of times we're supposed to repeat
var t = typeof times == "undefined" ? Infinity : times;
// The tick function we call
function tick() {
// Set up the next iteration if appropriate
// Note that --Infinity is Infinity, so it will never stop in that case
if (--t > 0) {
setTimeout(tick, wait);
}
// Call the function, canceling further repeats if it throws an exception
try {
func.call(null); // Or just func() seems like it should be good enough, really
} catch(e) {
t = 0;
throw e;
}
}
// Start the process
setTimeout(tick, wait);
}
That preserves a couple of behaviors of the current function which are different from the standard setInterval (I can't speak for Angular's):
It stops trying to call the function if the function fails once
It has a feature letting you specify the max number of times to call the function (which seems handy)

javascript setInterval() collision between the delay and execution time

Is there a way to avoid the conflict between the delay and execution time if the time of execution was longer than the delay using setInterval()?
For example:
setInterval(function(){
// some code that takes 300 ms to be executed
// which it's longer than the delay 200 ms
}, 200);
I already found the alternate way, which is to use setTimeout() with recursion to ensure that the delay will start immediately after the function is executed, but my question is about setInterval(), not replacing it with setTimeout()
I'm not sure what is your concern.
Javascript is always single-threaded that means that in time of execution of the function called by setInterval no other function will be executed and no re-run of setInterval may happen!
Naturally if in your setInterval called function you use deferred calls you enable the function to finish and be executed again.
To protect against such problem you may use a simple semaphore like:
var inProcessing = false ;
setInterval(function(){
// some code that takes 300 ms to be executed
// which it's longer than the delay 200 ms
if (!inProcessing){
inProcessing = true ;
$http.get(...).then(function(){inProcessing = false;...},
function(){inProcessing = false;...});
}
}
}, 200);
You cannot do this using setInterval, only setTimeout. If your problem is the lack of easy cancellation of the setTimeout method, you can use the following:
var timeout = setTimeout(function runMe(){
// some code that takes 300 ms to be executed
timeout = setTimeout(runMe, 200);
}, 200);
// somewhere else
clearTimeout(timeout);
You can use a nested setTimeout instead of setInterval. Hope you enjoy !
https://javascript.info/settimeout-setinterval
I'm assuming you just want to postpone a cycle of setInterval if the code from a previous run isn't complete.
var starts = 0;
var ends = 0;
function myFunc () {
starts++;
//doStuff
ends++;
}
setInterval(function () {
if (starts === ends) {
myFunc();
}
}, 200);

Callbacks that don't block

I would like to write a function that takes a callback and calls it after the function is done.
This is easy:
var callback = function (ref) {
var i = 1337;
while (i--) {
console.log(ref, 'callback');
}
};
var someFoo = function (ref, callback) {
console.log(ref, 'self');
callback(ref);
}
someFoo('one', callback); // 1
someFoo('two', callback); // 2
But here I'm facing this problem: First the someFoo call blocks until the allback is finished. That means this code is equivalent to this (which blocks until each function is finished):
someFoo('one');
callback('one');
someFoo('two');
callback('two');
Now the question: How to make the callback call asynchronous?
Change:
callback(ref);
To:
setTimeout(function(){ callback(ref); }, 0);
Or, since you are writing a chrome extension, and, therefore, don't have to worry about older browsers, you can use bind:
setTimeout(callback.bind(null, ref), 0);
JavaScript as implemented in browsers is single-threaded so you cannot do a real asynchronous call. What you can do is something like this to approximate that:
setTimeout(function() {
callback(ref);
}, 1000);
Where 1000 is 1 second in milliseconds (delay further as needed). However, as it is single-threaded that callback will still block other code that was running.
New browsers support web workers, but using web workers to approximate threading will leave you with code that won't work with many older browsers and not all newer browsers support the full spec even now.

Execution order of multiple setTimeout() functions with same interval

Consider the following Javascript code:
function(){
setTimeout(function() {
$("#output").append(" one ");
}, 1000);
setTimeout(function() {
$("#output").append(" two ");
}, 1000);
}
You can also see this example on jsfiddle.
Can I be sure that the value of #output is always "one two", in that order? Usually, I would handle this problem like this:
function(){
setTimeout(function() {
$("#output").append(" one ");
$("#output").append(" two ");
}, 1000));
}
But I can not do it that way because I get messages from a server which tells me which function to execute (in this example append "one" or append "two"), which I have to execute with a small delay.
I already tested this code in Internet Explorer 9, Firefox 14, Chrome 20 and Opera 12, and the output was always "one two", but can I be sure that this will always be the case?
The Spec is here.
My interpretation of setTimeout step 8 in section 7.3 is that the execution order is supposed to be guaranteed.
However, I investigated this issue because when the window is minimized and then maximised in Chrome, I was finding that timeouts set in events coming from external sources (like websockets or webworkers) were being executed in the wrong order. I assume this is a browser bug and will hopefully be fixed soon.
Play around with this in your fiddle
$(document).ready(function() {
setTimeout(function() {
$("#output").append(" one ");
}, 1000);
});
$(document).ready(function() {
setTimeout(function() {
$("#output").append(" two ");
}, 999);
});​
And you will see that both
output: one two
output: two one
are possible. So Speransky is right that you cannot rely on your timeouts executing in the same order always.
Note that I have change one time with 1ms to show that the timeout 1000ms can execute before the 999ms timeout.
EDIT: The below code can delay execution without any chance of two to be printed before one
function(){
setTimeout(function() {
$("#output").append(" one ");
setTimeout(function() {
$("#output").append(" two ");
}, 100);
}, 1000);
}
Yes, because javascript code is executed in one single thread, all asynchronous events, like click, mousemove, are queued to execute. When you call setTimeout, the engine insert a timer into its queue to execute in future, at least after delay time. So the two setTimeout generate two timers, one after another.
You can have a look at How Javascript Timers Work by John Resig.
No, you cannot be sure. It is asynchronously.
But in fact it probably will be true, because of realization of the mechanism in browsers.
Yes, the output will always be "one two".
Because the first setTimeout always runs before the second setTimeout, if they have same delay time, then the callback function of first will always be executed before the callback function of the second.
Update
I've updated your fiddle with this method. Check it out here
How about this method -- Get a list of things to do from your server --
//Example - append one, append two
var appendList = ['one', 'two']
//then do this
appendList.forEach(function(item, index){
setTimeout(function(){
$('#output').append(item)
}, index * 50 + 1000);
});
This way, you decide the sequence.
If events are synchronous, there is the Continuum function to run functions in sequence:
function keyedSequence(key, fn) {
fn = fn || this;
key.push({fn:fn});
return function() {
for(var i=0, n, full=1; i<key.length; i++) {
n = key[i];
if(n.fn == fn) {
if(!n.called) n.called = 1, n.args = key.slice.call(arguments);
if(!full) break
}
if(!n.called) full = 0
}
if(full) for(i=0; i<key.length; i++)
n = key[i], key[i] = {fn:n.fn}, n.fn.apply(n, n.args);
}
}
Function.prototype.seq = keyedSequence;
You provide an empty array as the key. Functions keyed with the same key will be grouped together.
window.onload = function() {
var key = [];
document.getElementById("test1").addEventListener('click', function1.seq(key), false);
document.getElementById("test2").addEventListener('click', function2.seq(key), false);
}
Click test2, then click test1 and order of execution is still function1 then function2.
Another way of calling it is:
window.onload = function() {
var key = [];
document.getElementById("test1").addEventListener('click', keyedSequence(key, function1), false);
document.getElementById("test2").addEventListener('click', keyedSequence(key, function2), false);
}
It depends on the browser, because setTimout is part of the window object in the browser, not defined in ECMAScript.
Per spec, the have the same task source, and things that have the same task source should be ordered. So yes.

Categories

Resources