I have a fairly standardised revealing module pattern to create objects for use as instances. Sometimes, within these patterns there are timeouts or intervals that need to be cancelled in the case that the module is no longer being used or referenced within external code.
Simplified example of this pattern:
function test() {
window.timer = maketimer();
}
function maketimer() {
var cls, my;
my = {
increment: 0,
timerid: null,
exec_timer: function() {
my.timerid = window.setInterval(my.time, 2000);
},
time: function() {
console.log("timer: ", my.timerid, my.increment++);
}
},
cls = {
//...
}
my.exec_timer();
return cls;
};
test();
// some time later...
test();
In the case that test is called twice, for whatever reason, the variable window.timer is replaced with a second instance of maketimer but the first instance timer continues to run.
A lot of the time, my modules are intrinsically linked to DOM nodes, and more often than not the DOM nodes are removed with the old instances, so I could in theory check for the non-existence of the node or its placement outside of the DOM, and then cancel the interval in this case.
This is far more generic however, and I would like to be able to deal with timeouts outside of the DOM environment.
In this case I would wrap the whole function in an IIFE that contains an instance variable. In it, you save the timer. And every time a new one is started, the old one is destroyed:
(function(window) {
var timerInstance = null;
window.maketimer = function() {
var cls, my;
if(timerInstance) {
timerInstance.destroyInstance();
}
my = {
increment: 0,
timerid: null,
exec_timer: function() {
my.timerid = window.setInterval(my.time, 2000);
},
time: function() {
console.log("timer: ", my.timerid, my.increment++);
},
destroyInstance: function() {
window.clearInterval(my.timerid);
}
},
cls = {
//...
}
my.exec_timer();
timerInstance = my;
return cls;
}
})(window);
function test() {
window.timer = maketimer();
}
test();
test();
Just out of curiosity, why do you need to have the instance on a global variable? window.timer is pretty generic and could be overridden by other scripts.
Try it: Update your code below..
var settimer;
function test() {
clearTimeout(settimer);
settimer= setTimeout(function () {
window.timer = maketimer();
}, 100);
}
Expanding on the answer given by #nils, I've created the below example for constructing a module which takes in a single DOM node and will only clear the previous instance/timer if the DOM node has already been used:
<div id="element1">[code here that may be updated with XHR]</div>
<div id="element2">[code here that may be updated with XHR]</div>
(function(window) {
var timerInstances = [];
window.maketimer = function(element) {
var cls, my, a;
// find instances where the passed element matches
for (a = 0; a < timerInstances.length; a += 1) {
if (timerInstances[a].element === element) {
console.log("instance already exists for element", element, "destroying...");
timerInstances[a].in.destroyInstance();
}
}
my = {
increment: 0,
timerid: null,
exec_timer: function() {
my.timerid = window.setInterval(my.time, 2000);
},
time: function() {
console.log("timer: ", my.timerid, my.increment++);
},
destroyInstance: function() {
window.clearInterval(my.timerid);
}
},
cls = {
//...
}
my.exec_timer();
timerInstances.push({
'element': element,
'in': my
});
return cls;
}
})(window);
function test(element) {
window.timer = maketimer(element);
}
test(document.getElementById("element1")); // produces 1 timer
test(document.getElementById("element1")); // cancels last, produces 1 timer
test(document.getElementById("element2")); // produces 1 timer
The identifying argument could be anything here - in this case it's a DOM node, but it could easily be a Number, String etc.
The accepted answer is very helpful and is still preferred if you wish to maintain the rule of having one instance at a time on the document.
Related
I'm trying to cancel a requestAnimationFrame loop, but I can't do it because each time requestAnimationFrame is called, a new timer ID is returned, but I only have access to the return value of the first call to requestAnimationFrame.
Specifically, my code is like this, which I don't think is entirely uncommon:
function animate(elem) {
var step = function (timestamp) {
//Do some stuff here.
if (progressedTime < totalTime) {
return requestAnimationFrame(step); //This return value seems useless.
}
};
return requestAnimationFrame(step);
}
//Elsewhere in the code, not in the global namespace.
var timerId = animate(elem);
//A second or two later, before the animation is over.
cancelAnimationFrame(timerId); //Doesn't work!
Because all subsequent calls to requestAnimationFrame are within the step function, I don't have access to the returned timer ID in the event that I want to call cancelAnimationFrame.
Looking at the way Mozilla (and apparently others do it), it looks like they declare a global variable in their code (myReq in the Mozilla code), and then assign the return value of each call to requestAnimationFrame to that variable so that it can be used any time for cancelAnimationFrame.
Is there any way to do this without declaring a global variable?
Thank you.
It doesn't need to be a global variable; it just needs to have scope such that both animate and cancel can access it. I.e. you can encapsulate it. For example, something like this:
var Animation = function(elem) {
var timerID;
var step = function() {
// ...
timerID = requestAnimationFrame(step);
};
return {
start: function() {
timerID = requestAnimationFrame(step);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation = new Animation(elem);
animation.start();
animation.cancel();
timerID; // error, not global.
EDIT: You don't need to code it every time - that's why we are doing programming, after all, to abstract stuff that repeats so we don't need to do it ourselves. :)
var Animation = function(step) {
var timerID;
var innerStep = function(timestamp) {
step(timestamp);
timerID = requestAnimationFrame(innerStep);
};
return {
start: function() {
timerID = requestAnimationFrame(innerStep);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation1 = new Animation(function(timestamp) {
// do something with elem1
});
var animation2 = new Animation(function(timestamp) {
// do something with elem2
});
This is the question:
Define a function named print which just print out the parameters it gets.
But it will not print out anything if it's called normally.
Only in a setTimeout callback will become effective.
e.g:
setTimeout(function() {
print('123'); //===> 123
});
print('456'); //===> nothing output
I have one solution but I don't think it's a good way, I rewrite the setTimeout.
I want a better solution curiously.
var print = function() {
'use strict';
var __origSetTimeout = window.setTimeout;
window.setTimeout = function(fn, delay) {
var _fn = new Function(`(${fn.toString().replace(/print\(/g, 'print.call(this,')}).call(this);`);
return __origSetTimeout.call(window, _fn.bind({
isFromSetTimeout: true
}), delay);
};
return function print(word) {
if (!this || !!this && !this.isFromSetTimeout) return;
console.log(word);
};
}.call(null);
You can use scope to solve this, for example
function A(){
let print = function(str){
console.log(str);
}
this.setTimeout = function(){
setTimeout(function(){
print('123');
}, 1000);
}
}
let a = new A();
a.setTimeout();
You could use a monkey patch for an extension of the print function with an additional check for a this object and a property for printing.
// simple function with output
function print(s) {
console.log(s);
}
// apply monkey patch
void function () {
var p = print;
print = function () {
if (this && this.timeout) {
p.apply(this, arguments);
}
}
}();
// bind additional information
setTimeout(print.bind({ timeout: true }, '123'));
print('456');
I'm trying to cancel a requestAnimationFrame loop, but I can't do it because each time requestAnimationFrame is called, a new timer ID is returned, but I only have access to the return value of the first call to requestAnimationFrame.
Specifically, my code is like this, which I don't think is entirely uncommon:
function animate(elem) {
var step = function (timestamp) {
//Do some stuff here.
if (progressedTime < totalTime) {
return requestAnimationFrame(step); //This return value seems useless.
}
};
return requestAnimationFrame(step);
}
//Elsewhere in the code, not in the global namespace.
var timerId = animate(elem);
//A second or two later, before the animation is over.
cancelAnimationFrame(timerId); //Doesn't work!
Because all subsequent calls to requestAnimationFrame are within the step function, I don't have access to the returned timer ID in the event that I want to call cancelAnimationFrame.
Looking at the way Mozilla (and apparently others do it), it looks like they declare a global variable in their code (myReq in the Mozilla code), and then assign the return value of each call to requestAnimationFrame to that variable so that it can be used any time for cancelAnimationFrame.
Is there any way to do this without declaring a global variable?
Thank you.
It doesn't need to be a global variable; it just needs to have scope such that both animate and cancel can access it. I.e. you can encapsulate it. For example, something like this:
var Animation = function(elem) {
var timerID;
var step = function() {
// ...
timerID = requestAnimationFrame(step);
};
return {
start: function() {
timerID = requestAnimationFrame(step);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation = new Animation(elem);
animation.start();
animation.cancel();
timerID; // error, not global.
EDIT: You don't need to code it every time - that's why we are doing programming, after all, to abstract stuff that repeats so we don't need to do it ourselves. :)
var Animation = function(step) {
var timerID;
var innerStep = function(timestamp) {
step(timestamp);
timerID = requestAnimationFrame(innerStep);
};
return {
start: function() {
timerID = requestAnimationFrame(innerStep);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation1 = new Animation(function(timestamp) {
// do something with elem1
});
var animation2 = new Animation(function(timestamp) {
// do something with elem2
});
Can anyone please tell my why this doesn't work?
(function() {
window.Test = {};
})();
Test.Timers = {
c: null,
startTimer: function() { c = 0; setTimeout(this.doWork, 0); },
doWork: function() {
c++;
alert(c);
setTimeout(this.doWork, 0);
}
};
When I call Test.Timers.startTimer(), it only alerts once with a 1.
Thanks
A method doesn't "remember" its owner (its this); you can copy a method from one object to another, and treat it like any other function. It only has the right owner when you actually call it using dot-notation, e.g. this.doWork().
So your problem is that you're passing the function this.doWork to setTimeout, and then it gets called as a function without knowing its owner, and suddenly its this is window instead of your timer object. To fix this, you need to keep track of your this yourself. For example, you might write:
Test.Timers = (function () {
var newTimer = {
c: null,
startTimer: function() {
this.c = 0;
setTimeout(function () { newTimer.doWork(); }, 0);
},
doWork: function() {
this.c++;
alert(this.c);
setTimeout(function () { newTimer.doWork(); }, 0);
}
};
return newTimer;
})();
or:
Test.Timers = (function () {
var startTimer = function() {
newTimer.c = 0;
setTimeout(doWork, 0);
};
var doWork = function() {
newTimer.c++;
alert(newTimer.c);
setTimeout(doWork, 0);
};
var newTimer = {
c: null,
startTimer: startTimer,
doWork: doWork
};
return newTimer;
})();
(Note that I also changed c to this.c or newTimer.c where necessary, since your version refers repeatedly to window.c. Also note that in the second version, if you don't need external code to be able to access c, you can change it to a local variable, making things cleaner.)
As per your comment to ruakh's answer, I prefer the following approach, myself:
Test.Timers = (function () {
var this_ = {
c: null,
startTimer: function() { this_.c = 0; setTimeout(this_.doWork, 0); },
doWork: function() {
this_.c++;
alert(this_.c);
setTimeout(this_.doWork, 0);
}
};
return this_;
})();
That way, the meaning is clear as this_ looks like this, and all you have to do is get used to the closure pattern of making an anonymous function and calling it right away. Also note that I fixed your reference to c to refer to this_.c instead of a global variable c.
Alternatively, you can use .bind() to bind the function's this to a particular thing. This is built-in to Chrome's V8, at least, and perhaps Firefox as well:
Test.Timers = {
c: null,
startTimer: function() { this.c = 0; setTimeout(this.doWork, 0); },
doWork: function() {
this.c++;
alert(this.c);
setTimeout(this.doWork, 0);
}
};
Test.Timers.startTimer = Test.Timers.startTimer.bind(Test.Timers);
Test.Timers.doWork = Test.Timers.doWork.bind(Test.Timers);
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!