I have a situation where multiple setTimeout() calls (typical length: 100ms to 1 sec) are active and should have gone off, but they do not fire. A Chrome (Mac) debugger profile shows "idle" during this (potentially infinite) period. The profile shows that nothing is going on. No loops. No code running. No garbage collection. No activity whatsoever. The viewport is active and has focus. After I wait (potentially an infinite time), when I "do something else", like mouseover some unrelated element -- as simple as a :hover -- the logjam breaks and the queued setTimeout()s all fire.
When I set breakpoints in the setTimeout() handler functions after this "freeze" occurs, they get hit in sequence when the logjam breaks, just as you would expect.
Unfortunately the replication path is difficult. So far, creating simpler test cases just makes replication even more difficult or, ultimately, impossible.
Most of the chatter around setTimeout() "issues" is from people who don't understand the single-thread nature of jscript, etc., so it is not helpful. Let me repeat: the timers are queued and should have fired. The browser is idle, as proved by the profiler. The timers DO fire ultimately, but only after mouse activity occurs. This behavior seems very wrong to me. If the browser is idle, and there are events in the queue, they should fire.
Has anyone seen behavior like this? Have I stumbled across a way to lock up the event dispatcher? Maybe I'm missing something.
Update: Cannot replicate on Windows 7.
Update 2: Restarted Chrome on Mac, can no longer replicate. So, worst possible outcome: no answer as to why it happened, why it kept happening, why it didn't happen reliably, why it went away, and why it won't happen any more.
I had a similar issue recently and discovered that since version 47, chromium folks have decided to not honour setTimeout when they believe this is 'detrimental to the majority of users'. Basically they've deprecated the setTimeout API (asking no one, as usual).
Here is bug 570845 where people discovered about this. There are a number of other bugs and discussion threads regarding the issue.
The fallback is to emulate setTimeout using requestAnimationFrame.
Here is a proof of concept:
'use strict'
var PosfScheduler = ( function () {
/*
* Constructor.
* Initiate the collection of timers and start the poller.
*/
function PosfScheduler () {
this.timers = {};
this.counter = 0;
this.poll = function () {
var scheduler = this;
var timers = scheduler.timers;
var now = Date.now();
for ( var timerId in timers ) {
var timer = timers[timerId];
if ( now - timer.submitDate >= timer.delay ) {
if ( timer.permanent === true ) {
timer.submitDate = now;
} else {
delete timers[timer.id];
}
timer.func.apply.bind( timer.func, timer.funcobj, timer.funcargs ).apply();
}
}
requestAnimationFrame( scheduler.poll.bind(scheduler) );
};
this.poll();
};
/*
* Adding a timer.
* A timer can be
* - an interval (arg[0]: true) - a recurrent timeout
* - a simple timeout (arg[0]: false)
*/
PosfScheduler.prototype.addTimer = function () {
var id = this.counter++;
var permanent = arguments[0] ;
var func = arguments[1] ;
var delay = arguments[2] ;
var funcobj = arguments[3] ;
var funcargs = Array.prototype.slice.call(arguments).slice(4);
var submitDate = Date.now() ;
var timer = {
id: id,
permanent: permanent,
func: func,
delay: delay,
funcargs: funcargs,
submitDate: submitDate,
}
this.timers[id] = timer;
return timer;
};
/*
* Replacement for setTimeout
* Similar signature:
* setTimeout ( function, delay [obj,arg1...] )
*/
PosfScheduler.prototype.setTimeout = function () {
var args = Array.prototype.slice.call(arguments);
return this.addTimer.apply.bind( this.addTimer, this, [false].concat(args) ).apply();
};
/*
* Replacement for setInterval - Untested for now.
* Signature:
* setInterval ( function, delay [obj,arg1...] )
*/
PosfScheduler.prototype.setInterval = function () {
var args = Array.prototype.slice.call(arguments);
return this.addTimer.apply.bind( this.addTimer, this, [true].concat(args) ).apply();
};
PosfScheduler.prototype.cancelTimeout = function ( timer ) {
delete this.timers[timer.id];
};
/*
* Don't want to leave all these schedulers hogging the javascript thread.
*/
PosfScheduler.prototype.shutdown = function () {
delete this;
};
return PosfScheduler;
})();
var scheduler = new PosfScheduler();
var initTime = Date.now();
var timer1 = scheduler.setTimeout ( function ( init ) {
console.log ('executing function1 (should appear) after ' + String ( Date.now() - init ) + 'ms!' );
}, 200, null, initTime );
var timer2 = scheduler.setTimeout ( function ( init ) {
console.log ('executing function2 afte: ' + String ( Date.now() - init ) + 'ms!' );
}, 300, null, initTime );
var timer3 = scheduler.setTimeout ( function ( init ) {
console.log ('executing function3 (should not appear) after ' + String ( Date.now() - init ) + 'ms!' );
}, 1000, null, initTime );
var timer4 = scheduler.setTimeout ( function ( init, sched, timer ) {
console.log ('cancelling timer3 after ' + String ( Date.now() - init ) + 'ms!' );
sched.cancelTimeout ( timer3 );
}, 500, null, initTime, scheduler, timer3 );
var timer5 = scheduler.setInterval ( function ( init, sched, timer ) {
console.log ('periodic after ' + String ( Date.now() - init ) + 'ms!' );
}, 400, null, initTime, scheduler, timer3 );
var timer6 = scheduler.setTimeout ( function ( init, sched, timer ) {
console.log ('cancelling periodic after ' + String ( Date.now() - init ) + 'ms!' );
sched.cancelTimeout ( timer5 );
}, 900, null, initTime, scheduler, timer5 );
Related
I'm trying to build a carousel that:
Has an auto-timer ( every X seconds )
Has directional arrows ( that when used, should reset the auto-timer to X seconds again )
I had this using the setInterval function but the problem was even worse, so I decided to change it for setTimeout and call it recursively.
So I have:
The timer variable
this.timeOut = false;
These two functions, to enable the carousel or stop it:
stopSlide() {
console.log('stop', this.timeOut);
if (this.timeOut) {
window.clearTimeout(this.timeOut);
this.timeOut = null;
delete this.timeOut;
}
}
startSlide() {
this.stopSlide();
const hasMoreThanOneSlide = this.ribbon.querySelectorAll('.pts-desktop-header-ribbon__item').length > 1;
this.timeOut = hasMoreThanOneSlide ? window.setTimeout(() => this.doPlay(this.play), this.time) : null;
console.log('start', this.timeOut);
}
And this one that plays it (removing some index's vars and stuff so the question is not too large):
doPlay(element) {
const newScroll = element.classList.contains(BUTTON_LEFT_CLASS) ? 1 : -1;
const totalItems = this.el.querySelectorAll(`.${BASE_LIST_ITEM}`).length - 1;
/* Update current */
this.current++; // this wasn't like this cause actually i can turn +1 or -1 but I don't want to make this question longer
/* Update visible */
this.lists.forEach((list) => {
list.style.transform = `translate(${-this.current * 100}%)`;
});
/* Update visible class */
this.updateCurrentRibbonElement();
if (this.timeOut) {
this.stopSlide();
this.startSlide(); // Calls the setTimeout function again
} else {
this.stopSlide();
}
}
And this is how I init it:
events.delegate(this.el, `.${BUTTON_CLASS}`, 'click', (e) => {
this.stopSlide();
this.doPlay(e.elementTarget);
this.hideRibbon();
});
/* Start interval */
this.startSlide();
Full class (in Webpack syntax) can be found here: https://jsfiddle.net/toz4xafu/
So the problem is, that Sometimes, when using the arrows very fast ( stressing the app ) seems that is not clearing the this.timeOut var and it's multiple times executed ( instead of 7 seconds it's 2-4 seconds )
The question is, how can I make sure that I won't have more than one timeout running?
By the way, is there any way to debug how many timmers are executing?
Background
Imagine you have an app where you receive tons of info when you make a request. In my app about restaurants, I want to calculate the average price of all menus every time I get a request containing several restaurants:
var avg = menus => {
const sum = menus.reduce( ( sum, currMenu ) => currMenu.price + sum , 0);
console.log( sum / menus.length );
};
Our Solution
The problem is that I get so many restaurants my app starts to lag! Solution? Well, since the user doesn't care to see the updated average every 0.001 ms we decided to use debounce to show an updated average every 100ms or so:
var debounceAvg = _.debounce( avg, 100 );
For an article on how debounce works have a read at here:
https://css-tricks.com/debouncing-throttling-explained-examples/
Problem
So, now we have a debounce function. To test it I have a test function:
var myMenus = [ { price: 1 }, { price: 2 }, { price: 3 } ];
var timer = setInterval( params => {
console.log("executing timer");
debounceAvg(params);
}, 20, myMenus );
setTimeout( () => { clearInterval(timer) }, 5000);
This function should print to the log the avg price of all menus every 100ms even though it is being invoked every 20ms. However I only end up with 2 logs containing the avg price - the first and the last.
Question
What am I doing wrong ?
You are actually logging outside your debounce function. So your logging is happening every time through the loop. That's why you are getting all those zeros.
You can either do your logging inside your debounce function (you could pass it in as a parameter), or check for a change in your loop.
Here is a possible implementation of passing your function in as a param for testing. I'm not sure that I fully understand your logic in the functions, but this should at least get you started:
var val = 0;
avg = function(menus, callback) {
val = menus.reduce( ( sum, currMenu) => currMenu.price + sum , 0)) / menus.length;
callback();
return val;
}
var testDebounce = iterations => {
const myMenus = [];
for( let i = 0; i < iterations; i++ ) {
myMenus.push( { price: i } );
debounceAvg(aparts, function() {
console.log(val);
});
}
};
I am using debouncing to execute events after a timeout using settimeout. The problem I have, is that other javascript events expect those events to occur synchronously. Since they are now executing after a timeout, I'd like to be able to trigger them prematurely by other javascript events (so those events requiring them won't fail).
Anywhom, if I do something like:
timeout = setTimeout(function() { alert('hi'); }, 10000);
, and I want that to occur before 10 seconds passes, how can I do that?
The solution can involve jquery if necessary. Thanks!
Edit:
Is it possible to do this with just access to the timeout object?
So, if you make whatever you're delaying its own function:
function sayHi() {
alert('hi');
}
You can use a timeout and a regular function call:
var timeout = setTimeout(sayHi, 10000); // say hi after 10 seconds
Or to call it before the timeout expires, just call the function whenever you need to:
sayHi();
Am I on the right track here? If you need to cancel the timeout, call clearTimeout() on your timeout variable.
if (timeout)
clearTimeout(timeout);
You cannot track it with the standard setTimeout, but Javascript allows you to enhance features as you wish.
For example you could have your own enhanced setTimeout:
var _setTimeout = window.setTimeout;
var timeouts = [];
window.setTimeout = function(fn, ms) {
var id = _setTimeout(fn, ms);
timeouts[id] = fn;
return id;
};
window.premature = function(id) {
var fn = timeouts[id];
if (fn) {
clearTimeout(id);
if (fn instanceof String) {
eval(fn);
} else {
fn()
}
}
};
function printDate(str) {
$("body").append("<p>" + str + ". " + new Date() + "</p>");
}
$(function() {
var id1 = setTimeout(function() { printDate("id1"); }, 10000);
var id2 = setTimeout(function() { printDate("id2"); }, 10000);
printDate("first one");
// just to demonstrate that the string version works too
setTimeout("window.premature(" + id1 +")", 5000);
});
You can see it in action at jsFiddle
Do note, that this simple hack does not take into account clearing used ids when the timeouts do occur, but it is just to show that you can do this sort of thing in Javascript if you really need it.
polyfill solution
Here is some javascript that I've updated from a previous project, it is extended now with trigger and update methods; it is similar to Jan Wikholm's solution (+1) — but a bit more complete, taking into account clears, passing of arguments, and the prevention of eval if required:
(function(keep){
/// a few things to remember
keep.setTimeout = window.setTimeout;
keep.clearTimeout = window.clearTimeout;
keep.TO = function(){};
keep.list = {};
keep.settings = {
eval: false /// set this to true if you wish to support string timeouts
};
/**
* Quick check function to prevent eval
*/
keep.checkParam = function( param ){
if ( !keep.settings.eval && typeof param == 'string' ) {
throw new Error('setTimeout blocked evaluation of string, ' +
'use a function instead.');
return false;
}
else if ( param ) {
return true;
}
};
/**
* Simple function constructor to avoid trapping unwanted references
*/
keep.makeFunction = function(data){
return function(args){
/// copy our args array
args = data.args.slice();
/// do we allow eval?
if ( keep.settings.eval ) {
/// if so, reuse setTimeout for it's abilities
args[0] = data.param; /// use the original param
args[1] = 0; /// trigger immediately
keep.setTimeout.apply( window, args );
}
// more secure, assume dealing with function -- not string
else if ( keep.checkParam( data.param ) && data.param.apply ) {
data.param.apply( window, args.slice(2) );
}
else {
throw new Error('unsupported param for setTimeout' +
' i.e. non-function used.');
}
/// clear our storage of this tid
window.clearTimeout( data.tid );
};
};
/**
* Sets timeouts just like you would expect
*/
window.setTimeout = function( param, timeout ){
if ( keep.checkParam( param ) ) {
var tid, data;
/// support passing a timeout object as param
if ( param instanceof keep.TO ) {
data = param;
data.args[1] = data.timeout;
}
else {
/// create an object to store the timeout info
data = new keep.TO();
data.func = keep.makeFunction(data);
data.param = param;
data.timeout = timeout;
data.args = Array.prototype.slice.call(arguments,0);
data.args[0] = data.func;
}
data.tid = keep.setTimeout.apply( window, data.args );
keep.list[data.tid] = data;
/// enhance the returned number to support .clear, .trigger and .update
tid = new Number(data.tid);
tid.clear = window.clearTimeout;
tid.trigger = window.triggerTimeout;
tid.update = window.updateTimeout;
return tid;
}
};
/**
* Clearing timeouts since 2013
*/
window.clearTimeout = function( tid ){
if ( this instanceof Number ) {
tid = 0 + this;
}
var obj;
if ( (obj = window.getTimeout(tid)) ) {
delete keep.list[tid];
keep.clearTimeout.call(window, tid);
}
};
/**
* Returns the internal timeout storage object
*/
window.getTimeout = function( tid ){
var obj;
if ( (obj = keep.list[tid]) ) {
return obj;
}
};
/**
* Clears and fires a timeout before it's outed time
*/
window.triggerTimeout = function( tid ){
if ( this instanceof Number ) {
tid = 0 + this;
}
var obj;
if ( (obj = window.getTimeout(tid)) ) {
window.clearTimeout(tid);
obj.func.call(window);
}
else {
throw new Error('No Timeout found to trigger for ID '+ tid);
}
};
/**
* Clears and recreates an existing timeout, returns a new timeout id.
*/
window.updateTimeout = function( tid, timeout ){
if ( this instanceof Number ) {
if ( arguments.length == 1 ) {
timeout = tid;
}
tid = 0 + this;
}
var obj;
if ( (obj = window.getTimeout(tid)) ) {
obj.timeout = timeout;
window.clearTimeout(tid);
return window.setTimeout(obj);
}
else {
throw new Error('No Timeout found to update for ID ' + tid);
}
};
/**
* Utility function to tidy up
*/
window.clearAllTimeouts = function(){
for ( var i in keep.list ) {
window.clearTimeout(i);
};
};
/// Tidy up
window.onunload = (function(previous){
return function(){
window.clearAllTimeouts();
keep.list = {};
previous && previous.call(window);
};
}(window.onunload));
})({});
include
Just put the above in a js file and include into your page using a normal script tag, the code does not need to be invoked in any way:
<script src="timeouts.js"></script>
usage
Obviously this should be used just like a normal setTimeout call, however you now have extra methods that should give more flexibility.
var tid = setTimeout( function(){ alert('OK Computer') }, 2000 );
For example you could cancel the original and force the timeout to trigger earlier:
setTimeout( function(){ triggerTimeout( tid ); }, 500 );
Or, you could update the timeout (make sure we remember the new returned tid):
setTimeout( function(){ tid = updateTimeout( tid, 5000 ); }, 500 );
You can also do the usual:
setTimeout( function(){ clearTimeout( tid ); }, 1000 );
Each of these methods are also accessible via the tid itself:
setTimeout( function(){ tid.trigger(); }, 1000 );
setTimeout( function(){ tid.update( 5000 ); }, 1000 );
setTimeout( function(){ tid.clear(); }, 1000 );
By default this code prevents the use of setTimeout with a string param, mainly because it is a far better coding style to pass functions rather than strings. To change this you can switch the following setting to true:
keep.settings = {
eval: true
};
This is not recommended however.
There is also an added benefit to leaving eval disabled, in the fact that the code will use normal function calling to trigger the timeout i.e. .apply(). This means that whatever browser you are using, you can pass arguments to the timeout function via setTimeout — which is not normally something you can rely on cross-browser. e.g:
setTimeout( function(a){ alert(a) }, 2000, 'Hello World' );
Just put the function out and give it a name:
function handler(){
alert('hi');
}
timeout = setTimeout(handler, 10000);
then you can call it in other places with handler();
Use clearTimeout and reschedule for an earlier time.
I'm writing some Javascript that interacts with library code that I don't own, and can't (reasonably) change. It creates Javascript timeouts used for showing the next question in a series of time-limited questions. This isn't real code because it is obfuscated beyond all hope. Here's what the library is doing:
....
// setup a timeout to go to the next question based on user-supplied time
var t = questionTime * 1000
test.currentTimeout = setTimeout( showNextQuestion(questions[i+1]), t );
I want to put a progress bar onscreen that fills towards questionTime * 1000 by interrogating the timer created by setTimeout. The only problem is, there seems to be no way to do this. Is there a getTimeout function that I'm missing? The only information on Javascript timeouts that I can find is related only to creation via setTimeout( function, time) and deletion via clearTimeout( id ).
I'm looking for a function that returns either the time remaining before a timeout fires, or the time elapsed after a timeout has been called. My progress bar code looks like this:
var timeleft = getTimeout( test.currentTimeout ); // I don't know how to do this
var $bar = $('.control .bar');
while ( timeleft > 1 ) {
$bar.width(timeleft / test.defaultQuestionTime * 1000);
}
tl;dr: How do I find the time remaining before a javascript setTimeout()?
Here's the solution I'm using now. I went through the library section that's in charge of tests, and unscrambled the code (terrible, and against my permissions).
// setup a timeout to go to the next question based on user-supplied time
var t = questionTime * 1000
test.currentTimeout = mySetTimeout( showNextQuestion(questions[i+1]), t );
and here's my code:
// wrapper for setTimeout
function mySetTimeout( func, timeout ) {
timeouts[ n = setTimeout( func, timeout ) ] = {
start: new Date().getTime(),
end: new Date().getTime() + timeout
t: timeout
}
return n;
}
This works pretty spot-on in any browser that isn't IE 6. Even the original iPhone, where I expected things to get asynchronous.
Just for the record, there is a way to get the time left in node.js:
var timeout = setTimeout(function() {}, 3600 * 1000);
setInterval(function() {
console.log('Time left: '+getTimeLeft(timeout)+'s');
}, 2000);
function getTimeLeft(timeout) {
return Math.ceil((timeout._idleStart + timeout._idleTimeout - Date.now()) / 1000);
}
Prints:
$ node test.js
Time left: 3599s
Time left: 3597s
Time left: 3595s
Time left: 3593s
This doesn't seem to work in firefox through, but since node.js is javascript, I thought this remark might be helpful for people looking for the node solution.
EDIT: I actually think I made an even better one: https://stackoverflow.com/a/36389263/2378102
I wrote this function and I use it a lot:
function timer(callback, delay) {
var id, started, remaining = delay, running
this.start = function() {
running = true
started = new Date()
id = setTimeout(callback, remaining)
}
this.pause = function() {
running = false
clearTimeout(id)
remaining -= new Date() - started
}
this.getTimeLeft = function() {
if (running) {
this.pause()
this.start()
}
return remaining
}
this.getStateRunning = function() {
return running
}
this.start()
}
Make a timer:
a = new timer(function() {
// What ever
}, 3000)
So if you want the time remaining just do:
a.getTimeLeft()
If you can't modify the library code, you'll need to redefine setTimeout to suit your purposes. Here's an example of what you could do:
(function () {
var nativeSetTimeout = window.setTimeout;
window.bindTimeout = function (listener, interval) {
function setTimeout(code, delay) {
var elapsed = 0,
h;
h = window.setInterval(function () {
elapsed += interval;
if (elapsed < delay) {
listener(delay - elapsed);
} else {
window.clearInterval(h);
}
}, interval);
return nativeSetTimeout(code, delay);
}
window.setTimeout = setTimeout;
setTimeout._native = nativeSetTimeout;
};
}());
window.bindTimeout(function (t) {console.log(t + "ms remaining");}, 100);
window.setTimeout(function () {console.log("All done.");}, 1000);
This is not production code, but it should put you on the right track. Note that you can only bind one listener per timeout. I haven't done extensive testing with this, but it works in Firebug.
A more robust solution would use the same technique of wrapping setTimeout, but instead use a map from the returned timeoutId to listeners to handle multiple listeners per timeout. You might also consider wrapping clearTimeout so you can detach your listener if the timeout is cleared.
Server side Node.js specific
None of the above really worked for me, and after inspecting the timeout object it looked like everything was relative to when the process started. The following worked for me:
myTimer = setTimeout(function a(){console.log('Timer executed')},15000);
function getTimeLeft(timeout){
console.log(Math.ceil((timeout._idleStart + timeout._idleTimeout)/1000 - process.uptime()));
}
setInterval(getTimeLeft,1000,myTimer);
Output:
14
...
3
2
1
Timer executed
-0
-1
...
node -v
v9.11.1
Edited output for brevity, but this basic function gives a approximate time until execution or since execution. As others mention, none of this will be exact due to the way node processes, but if I want to suppress a request that was run less than 1 minute ago, and I stored the timer, I don't see why this wouldn't work as a quick check. Could be interesting to juggle objects with refreshtimer in 10.2+.
Javascript's event stacks don't operate how you would think.
When a timeout event is created, it is added to the event queue, but other events may take priority while that event is being fired, delay the execution time and postponing runtime.
Example: You create a timeout with a delay of 10 seconds to alert something to the screen. It will be added to the event stack and will be executed after all current events are fired (causing some delay). Then, when the timeout is processed, the browser still continues to capture other events add them to the stack, which causes further delays in the processing. If the user clicks, or does a lot of ctrl+typing, their events take priority over the current stack. Your 10 seconds can turn into 15 seconds, or longer.
That being said, there are many ways to fake how much time has passed. One way is to execute a setInterval right after you add the setTimeout to the stack.
Example: Perform a settimeout with a 10 second delay (store that delay in a global). Then perform a setInterval that runs every second to subtract 1 from the delay and output the delay remaining. Because of how the event stack can influence actual time (described above), this still won't be accurate, but does give a count.
In short, there is no real way to get the remaining time. There are only ways to try and convey an estimate to the user.
A quicker, easier way:
tmo = 1000;
start = performance.now();
setTimeout(function(){
foo();
},tmo);
You can get the time remaining with:
timeLeft = tmo - (performance.now() - start);
I stopped by here looking for this answer, but was overthinking my problem. If you are here because you just need to keep track of time while you're setTimeout is in progress, here's another way to do it:
var focusTime = parseInt(msg.time) * 1000
setTimeout(function() {
alert('Nice Job Heres 5 Schrute bucks')
clearInterval(timerInterval)
}, focusTime)
var timerInterval = setInterval(function(){
focusTime -= 1000
initTimer(focusTime / 1000)
}, 1000);
You can modify setTimeout to store each timeout's end time in a map and create a function called getTimeout to get the time left for a timeout with a certain id.
This was super's solution, but I modified it to use slightly less memory
let getTimeout = (() => { // IIFE
let _setTimeout = setTimeout, // Reference to the original setTimeout
map = {}; // Map of all timeouts with their end times
setTimeout = (callback, delay) => { // Modify setTimeout
let id = _setTimeout(callback, delay); // Run the original, and store the id
map[id] = Date.now() + delay; // Store the end time
return id; // Return the id
};
return (id) => { // The actual getTimeout function
// If there was no timeout with that id, return NaN, otherwise, return the time left clamped to 0
return map[id] ? Math.max(map[id] - Date.now(), 0) : NaN;
}
})();
Usage:
// go home in 4 seconds
let redirectTimeout = setTimeout(() => {
window.location.href = "/index.html";
}, 4000);
// display the time left until the redirect
setInterval(() => {
document.querySelector("#countdown").innerHTML = `Time left until redirect ${getTimeout(redirectTimeout)}`;
},1);
Here's a minified version of this getTimeout IIFE:
let getTimeout=(()=>{let t=setTimeout,e={};return setTimeout=((a,o)=>{let u=t(a,o);return e[u]=Date.now()+o,u}),t=>e[t]?Math.max(e[t]-Date.now(),0):NaN})();
I hope this is as useful to you as it was for me! :)
No, but you can have your own setTimeout/setInterval for animation in your function.
Say your question looks like this:
function myQuestion() {
// animate the progress bar for 1 sec
animate( "progressbar", 1000 );
// do the question stuff
// ...
}
And your animation will be handled by these 2 functions:
function interpolate( start, end, pos ) {
return start + ( pos * (end - start) );
}
function animate( dom, interval, delay ) {
interval = interval || 1000;
delay = delay || 10;
var start = Number(new Date());
if ( typeof dom === "string" ) {
dom = document.getElementById( dom );
}
function step() {
var now = Number(new Date()),
elapsed = now - start,
pos = elapsed / interval,
value = ~~interpolate( 0, 500, pos ); // 0-500px (progress bar)
dom.style.width = value + "px";
if ( elapsed < interval )
setTimeout( step, delay );
}
setTimeout( step, delay );
}
If anyone's looking back on this. I've come out with a timeout and interval manager that can get you the time left in a timeout or interval as well as do some other stuff. I'll be adding to it to make it more nifty and more accurate, but it seems to work fairly well as is (although I have some more ideas to make it even more accurate):
https://github.com/vhmth/Tock
Question has already been answered but I will add my bit. It just occured to me.
Use setTimeout in recursion as follows:
var count = -1;
function beginTimer()
{
console.log("Counting 20 seconds");
count++;
if(count <20)
{
console.log(20-count+"seconds left");
setTimeout(beginTimer,2000);
}
else
{
endTimer();
}
}
function endTimer()
{
console.log("Time is finished");
}
I guess the code is self explanatory
Check this one:
class Timer {
constructor(fun,delay) {
this.timer=setTimeout(fun, delay)
this.stamp=new Date()
}
get(){return ((this.timer._idleTimeout - (new Date-this.stamp))/1000) }
clear(){return (this.stamp=null, clearTimeout(this.timer))}
}
Make a timer:
let smtg = new Timer(()=>{do()}, 3000})
Get remain:
smth.get()
Clear timeout
smth.clear()
(function(){
window.activeCountdowns = [];
window.setCountdown = function (code, delay, callback, interval) {
var timeout = delay;
var timeoutId = setTimeout(function(){
clearCountdown(timeoutId);
return code();
}, delay);
window.activeCountdowns.push(timeoutId);
setTimeout(function countdown(){
var key = window.activeCountdowns.indexOf(timeoutId);
if (key < 0) return;
timeout -= interval;
setTimeout(countdown, interval);
return callback(timeout);
}, interval);
return timeoutId;
};
window.clearCountdown = function (timeoutId) {
clearTimeout(timeoutId);
var key = window.activeCountdowns.indexOf(timeoutId);
if (key < 0) return;
window.activeCountdowns.splice(key, 1);
};
})();
//example
var t = setCountdown(function () {
console.log('done');
}, 15000, function (i) {
console.log(i / 1000);
}, 1000);
For anyone in need of a hook, check this out - should be pretty self explanatory.
Note that elapsed is an internal state variable that if passed outside of the hook will be incorrect!
import { useEffect, useRef, useState } from 'react';
const useTimeout = (callback, duration, renderDuration = 5) => {
const ref = useRef<any>(null);
const [timeInfo, setTimeInfo] = useState<{
start: number;
elapsed: number;
percentComplete: number;
}>({
start: null,
elapsed: 0,
percentComplete: 0
});
useEffect(() => {
return () => {
if (ref.current) {
clearTimeout(ref.current);
ref.current = null;
}
};
}, []);
useEffect(() => {
setTimeout(() => {
if (ref.current == null) return;
setTimeInfo((prev) => {
const elapsed = Date.now() - prev.start + prev.elapsed;
if (ref.current == null) return prev;
return {
start: prev.start,
elapsed: prev.elapsed,
percentComplete: (elapsed / duration) * 100
};
});
}, renderDuration);
}, [timeInfo]);
return {
percentComplete: timeInfo.percentComplete,
isTimerRunning: ref.current != null,
startTimeout: () => {
if (ref.current != null) return;
setTimeInfo((prev) => ({ ...prev, start: Date.now() }));
ref.current = setTimeout(callback, duration - timeInfo.elapsed);
},
stopTimeout: () => {
if (ref.current) {
clearTimeout(ref.current);
ref.current = null;
}
setTimeInfo((prev) => {
const elapsed = Date.now() - prev.start + prev.elapsed;
return {
start: prev.start,
elapsed: elapsed,
percentComplete: (elapsed / duration) * 100
};
});
},
resetTimeout: () => {
if (ref.current) {
ref.current = null;
clearTimeout(ref.current);
}
setTimeInfo({ start: null, elapsed: 0, percentComplete: 0 });
},
restartTimeout: () => {
if (ref.current) {
ref.current = null;
clearTimeout(ref.current);
}
setTimeInfo({ start: Date.now(), elapsed: 0, percentComplete: 0 });
ref.current = setTimeout(callback, duration);
}
};
};
export default useTimeout;
What is the most recommended/best way to stop multiple instances of a setTimeout function from being created (in javascript)?
An example (psuedo code):
function mouseClick()
{
moveDiv("div_0001", mouseX, mouseY);
}
function moveDiv(objID, destX, destY)
{
//some code that moves the div closer to destination
...
...
...
setTimeout("moveDiv(objID, destX, destY)", 1000);
...
...
...
}
My issue is that if the user clicks the mouse multiple times, I have multiple instances of moveDiv() getting called.
The option I have seen is to create a flag, that only allows the timeout to be called if no other instance is available...is that the best way to go?
I hope that makes it clear....
when you call settimeout, it returns you a variable "handle" (a number, I think)
if you call settimeout a second time, you should first
clearTimeout( handle )
then:
handle = setTimeout( ... )
to help automate this, you might use a wrapper that associates timeout calls with a string (i.e. the div's id, or anything you want), so that if there's a previous settimeout with the same "string", it clears it for you automatically before setting it again,
You would use an array (i.e. dictionary/hashmap) to associate strings with handles.
var timeout_handles = []
function set_time_out( id, code, time ) /// wrapper
{
if( id in timeout_handles )
{
clearTimeout( timeout_handles[id] )
}
timeout_handles[id] = setTimeout( code, time )
}
There are of course other ways to do this ..
I would do it this way:
// declare an array for all the timeOuts
var timeOuts = new Array();
// then instead of a normal timeOut call do this
timeOuts["uniqueId"] = setTimeout('whateverYouDo("fooValue")', 1000);
// to clear them all, just call this
function clearTimeouts() {
for (key in timeOuts) {
clearTimeout(timeOuts[key]);
}
}
// clear just one of the timeOuts this way
clearTimeout(timeOuts["uniqueId"]);
var timeout1 = window.setTimeout('doSomething();', 1000);
var timeout2 = window.setTimeout('doSomething();', 1000);
var timeout3 = window.setTimeout('doSomething();', 1000);
// to cancel:
window.clearTimeout(timeout1);
window.clearTimeout(timeout2);
window.clearTimeout(timeout3);
I haven't tested any of this, and just cut this up in the editor here. Might work, might not, hopefully will be food for thought though.
var Timeout = {
_timeouts: {},
set: function(name, func, time){
this.clear(name);
this._timeouts[name] = {pending: true, func: func};
var tobj = this._timeouts[name];
tobj.timeout = setTimeout(function()
{
/* setTimeout normally passes an accuracy report on some browsers, this just forwards that. */
tobj.func.call(arguments);
tobj.pending = false;
}, time);
},
hasRun: function(name)
{
if( this._timeouts[name] )
{
return !this._timeouts[name].pending;
}
return -1; /* Whut? */
},
runNow: function(name)
{
if( this._timeouts[name] && this.hasRun(name)===false )
{
this._timeouts[name].func(-1); /* fake time. *shrug* */
this.clear(name);
}
}
clear: function(name)
{
if( this._timeouts[name] && this._timeouts[name].pending )
{
clearTimeout(this._timeouts[name].timeout);
this._timeouts[name].pending = false;
}
}
};
Timeout.set("doom1", function(){
if( Timeout.hasRun("doom2") === true )
{
alert("OMG, it has teh run");
}
}, 2000 );
Timeout.set("doom2", function(){
/* NooP! */
}, 1000 );
Successive calls with the same identifier will cancel the previous call.
You could store multiple flags in a lookup-table (hash) using objID as a key.
var moving = {};
function mouseClick()
{
var objID = "div_0001";
if (!moving[objID])
{
moving[objID] = true;
moveDiv("div_0001", mouseX, mouseY);
}
}
You can avoid a global or lesser variable by using a property within the function. This works well if the function is only used for this specific context.
function set_time_out( id, code, time ) /// wrapper
{
if(typeof this.timeout_handles == 'undefined') this.timeout_handles = [];
if( id in this.timeout_handles )
{
clearTimeout( this.timeout_handles[id] )
}
this.timeout_handles[id] = setTimeout( code, time )
}
you can always overwrite the buttons onclick to return false. example:
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="UTF-8">
<head>
<title>Javascript example</title>
<script type="text/javascript">
var count = 0;
function annoy() {
document.getElementById('testa').onclick = function() { return false; };
setTimeout(function() {
alert('isn\'t this annoying? ' + count++);
document.getElementById('testa').onclick = window.annoy;
}, 1000);
}
</script>
</head>
<body>
<h2>Javascript example</h2>
Should Only Fire Once<br />
</body>
</html>
You can set a global flag somewhere (like var mouseMoveActive = false;) that tells you whether you are already in a call and if so not start the next one. You set the flag just before you enter the setTimeout call, after checking whether it's already set. Then at the end of the routine called in setTimeout() you can reset the flag.
I'm using this to force a garbage collection on all obsolete timeout references which really un-lagged my script preformance:
var TopObjList = new Array();
function ColorCycle( theId, theIndex, RefPoint ) {
...
...
...
TopObjList.push(setTimeout( function() { ColorCycle( theId, theIndex ,CCr ); },CC_speed));
TO_l = TopObjList.length;
if (TO_l > 8888) {
for (CCl=4777; CCl<TO_l; CCl++) {
clearTimeout(TopObjList.shift());
}
}
}
My original sloppy code was generating a massive array 100,000+ deep over a very short time but this really did the trick!