...

View Full Version : The Timer class, for object-oriented timeouts



Algorithm
11-27-2002, 12:10 AM
The problems with the setTimeout and setInterval functions provided in Javascript are twofold. First, you can't call a local object method without losing your scope, and second, you can't pass objects to the function, since the function call is implemented as a string.

The Timer class solves these difficulties by employing a static array to store the parent object and function arguments until the function is called.

This class is provided as-is and pro bono, so go ahead and muck with it if you see things that could be done better.

Thanks to WA for giving me the idea for this (albeit indirectly)!

Updated 4/18/2003: Footprint decreased, minor code improvements.
Updated 5/3/2003: Minor comment clarification; no code changes.
Updated 5/10/2003: Minor code improvements.

// The constructor should be called with
// the parent object (optional, defaults to window).

function Timer(){
this.obj = (arguments.length)?arguments[0]:window;
return this;
}

// The set functions should be called with:
// - The name of the object method (as a string) (required)
// - The millisecond delay (required)
// - Any number of extra arguments, which will all be
// passed to the method when it is evaluated.

Timer.prototype.setInterval = function(func, msec){
var i = Timer.getNew();
var t = Timer.buildCall(this.obj, i, arguments);
Timer.set[i].timer = window.setInterval(t,msec);
return i;
}
Timer.prototype.setTimeout = function(func, msec){
var i = Timer.getNew();
Timer.buildCall(this.obj, i, arguments);
Timer.set[i].timer = window.setTimeout("Timer.callOnce("+i+");",msec);
return i;
}

// The clear functions should be called with
// the return value from the equivalent set function.

Timer.prototype.clearInterval = function(i){
if(!Timer.set[i]) return;
window.clearInterval(Timer.set[i].timer);
Timer.set[i] = null;
}
Timer.prototype.clearTimeout = function(i){
if(!Timer.set[i]) return;
window.clearTimeout(Timer.set[i].timer);
Timer.set[i] = null;
}

// Private data

Timer.set = new Array();
Timer.buildCall = function(obj, i, args){
var t = "";
Timer.set[i] = new Array();
if(obj != window){
Timer.set[i].obj = obj;
t = "Timer.set["+i+"].obj.";
}
t += args[0]+"(";
if(args.length > 2){
Timer.set[i][0] = args[2];
t += "Timer.set["+i+"][0]";
for(var j=1; (j+2)<args.length; j++){
Timer.set[i][j] = args[j+2];
t += ", Timer.set["+i+"]["+j+"]";
}}
t += ");";
Timer.set[i].call = t;
return t;
}
Timer.callOnce = function(i){
if(!Timer.set[i]) return;
eval(Timer.set[i].call);
Timer.set[i] = null;
}
Timer.getNew = function(){
var i = 0;
while(Timer.set[i]) i++;
return i;
}
Here's an example of the code in action:

function Ticker(){
this.count = 0;
this.timer = new Timer(this);
}
Ticker.prototype.tick = function(d){
this.count+=d;
window.status = ""+this.count;
this.timer.setTimeout("tick", 1000, d);
}

window.onload = function(){
var ticker = new Ticker();
ticker.tick(1);
}

beetle
02-02-2003, 08:04 PM
Hey Algorithm

I don't know if anyone else here uses this, but I've implemented it into a couple scripts I'm working on that are OO in nature. I wouldn't be able to do them without a OO-based timeout include, and this one works wonderfully.

Thanks for the great code!

Skyzyx
02-05-2003, 02:07 AM
BTW, you CAN pass variables to setTimeout() and setInterval().



setTimeout('myFunction()', 1000, parameter1, parameter2, ..., parameter[n]);


But your code is still cool! :D

beetle
04-02-2003, 02:17 AM
Skyzyx. I've never seen that, and neither of these (http://www.mozilla.org/docs/dom/domref/dom_window_ref115.html#1021427) pages (http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/settimeout.asp) indicate that.

Have you actually used this?

Roy Sinclair
04-02-2003, 07:50 PM
Originally posted by Skyzyx
BTW, you CAN pass variables to setTimeout() and setInterval().



setTimeout('myFunction()', 1000, parameter1, parameter2, ..., parameter[n]);


But your code is still cool! :D

Sorry but that's a Netscape extension and it's not supported in IE. I may only be supported in Netscape 4, don't know if later versions of Netscape support it or not.

brothercake
04-02-2003, 08:51 PM
Originally posted by Algorithm
you can't pass objects to the function, since the function call is implemented as a string.
Yeah you can - I do this all the time:


var tempObj1,tempObj2,theTimer;
function someFunction(obj1,obj2)
{
tempObj1 = obj1;
tempObj2 = obj2;
theTimer = setInterval("someFunction(tempObj1,tempObj2)",100);
}

It may be passed as a string, but it still evaluates as an object reference.

beetle
04-02-2003, 09:08 PM
I think we've been talking about passing local variables, brothercake, at least I know I have. I mean, if you're using global variables there's no point in passing them at all :p

liorean
04-02-2003, 09:30 PM
Didn't Alex post a nice and small way to do this some time ago? (Like when I last frequented this forum, before the change of name from WSAbstract to JS Kit...)

Algorithm
04-19-2003, 12:28 AM
Thanks for the great feedback, guys, and thanks especially to beetle for his heavy promotion of the class.

I've updated the code to remove the global array and decrease the class's footprint by half, as well as to make minor improvements throughout the class. The code should run cleaner now and with less unnecessary delay.

Any feedback on this new version would be more than appreciated. :)

beetle
04-19-2003, 01:03 AM
Great! I'll d/l it right away.

No implementation changes, right? :P

Greak work :thumbsup:

Tim Scarfe
05-09-2003, 10:03 PM
Nice class.

Shame you are using strings on the setTimeout instead of passing a function reference.

Any syntax errors would not be clear to the developer as the string is evaluated at runtime. The code is ugly and there is simply no need for this uglyness. Simple scope guys..

We have commented on the script @ DC:

http://www.dhtmlcentral.com/forums/topic.asp?TOPIC_ID=17585

Algorithm
05-10-2003, 12:51 AM
Originally posted by Tim Scarfe
Shame you are using strings on the setTimeout instead of passing a function reference.

Any syntax errors would not be clear to the developer as the string is evaluated at runtime. The code is ugly and there is simply no need for this uglyness. Simple scope guys.. The problem is, there's numerous pitfalls involved in attempting to use a function reference. Passing it directly to window.setTimeout would nullify the scope (not to mention making the code inoperable in several browsers). Storing the reference for the duration would necessitate appending a temporary value to the parent object, and -- if at all possible -- altering external objects is something I emphatically want to avoid. The only other option would be to pass the method name along with the reference, which I consider redundant.

I agree that, if it were workable, passing a reference would be preferable to passing the name; however, I see no method in which it would work without alienating browsers or altering external objects. Feel free to prove me wrong.

Tim Scarfe
05-10-2003, 03:34 AM
The problem is, there's numerous pitfalls involved in attempting to use a function reference. Passing it directly to window.setTimeout would nullify the scope (not to mention making the code inoperable in several browsers). Storing the reference for the duration would necessitate appending a temporary value to the parent object, and -- if at all possible -- altering external objects is something I emphatically want to avoid. The only other option would be to pass the method name along with the reference, which I consider redundant.

Sorry, No. This is just wrong wrong wrong, and doesn't entirely make sense. Apart from this bit:


(not to mention making the code inoperable in several browsers).

*Scope is not lost
*You don't need to make any objects or properties or whatever you were saying
*Performance is greater with functions
*You want object instance code running in global scope, duh?
*You want to discover syntax errors at runtime?
*You want ugly code in strings? i.e. clearer code
*code is more encapsulated
*more clever things like applying functions possible

What you have done is just like using eval(), and we all know eval is evil, or you SHOULD.

As I said on DC, IE5.00 and some really ancient browsers have problems with this, but it's really a no brainer argument as proper scope is used in all properly written DHTML classes.

I'm way too tired to proove this now and I need sleep, but I have flagged this up on my to-do list for tommorrow, and it's a high priority! I've done well to post this much after the crazy night I've just returned from :)

In the mean time, I will quote Erik Advisson of webfx.eae.net fame:



// 27/05/2002 14:14

How come people keep using eval and the setTimeout eval counterpart. JS is a language that supports higher order functions and thus the best way to use setTimeout is to use the version that uses this feature. Compare the following two cases and decide for yourself which one to prefer.

// alt. 1
// non global code
var obj = getSomeObject();
makeObjectGloballyAccessible(obj); // in your case you put it in a global array
window.setTimeout(getGlobalAccessCode(obj) + ".someMethod()", t);

// alt. 2
// non global code
var obj = getSomeObject();
window.setTimeout(function() { obj.someMethod(); }, t);

The second one does not require any global references to objects and it allows you to use write code instead of strings. There are quite a few reasons to use alt. 2:

* Allows anonymous code, i.e. non-global objects
* Garbage collection can correctly dispose objects because there are no global references
* Easier to write
* Syntax errors are caught at startup.
* No eval. Evals are slow and error prone. Eval happen at runtime and cannot be compiled/checked. Even in a non compiled environment these are significantly slower than non-evaluated code.
* No need for code that makes object available as globals and no need for code to reference the global object

There is one downside to using the higher order function version of setTimeout and that is that it does not work in Netscape 3, IE3 (4?) and crashes IE5.0 (without any fixes, 5.01 works fine). But then again, targeting IE5.0 and NN4 and any of the old, non-dom browsers is not the choice of a true visionary :-)

erik

beetle
05-10-2003, 06:20 AM
Great arguments et points Tim - but I think you could step off the pedastal a bit (and Eric too)

Ignoring the fact that most people writing javascript aren't programmers is a bit pious. The fact of the matter is, many people (20%+) still use IE5 -- whether 5.0 or 5.01 I do not know, but when working for a client and not oneself, we have to keep these things in mind regardless of our capacity to be a "visionary".

This is great information to have and I thank you for that - I just think you could have presented your case with a bit more tact.

I don't think any more explanation is needed -- anyone that can grasp what scope is and understands OO js should have no trouble seeing what's going on in your last post.

Oh, and welcome to codingforums.com :D

WA
05-10-2003, 10:06 AM
Hi Tim:
Thanks for the info- it's certainly useful, and we do appreciate it. Personally though, the fact that this technique doesn't work in IE5 means I wouldn't even consider using it for now, since as someone mentioned, usability to me is just as important as elegance, if not more so.

Again, it's great information, though I may need to go back and study it some more to even get it. :)

Tim Scarfe
05-10-2003, 04:42 PM
Ok thanks guys.

Sorry, it could have been more tactful. I made the post with far too much haste!

eonics
05-11-2003, 12:31 PM
About Function arguments to timeouts:

A common mistake is to think that this does not work in IE5. It works in all versions of IE5 except the first publically released one. That is IE5.00 without any bug fixes or security fixes. This is a known bug in IE5.00 and was fixed by MS. As a matter of fact you cannot download a version that has this bug. The only way to get it is to use an Win2k/WinME out of the box without any securiy fixes or bug fixes which I definately would not recommend due to the numerous know security holes in that version.

If NN3/NN4 and IE3/4 is needed then I do agree that using an eval is the only way to go and as long as the interface is clean (yours is) I don't have any problems with it.

Keep up the good work

erik

brothercake
05-11-2003, 01:34 PM
Interesting ... Win2K straight out of the box is the IE5 I always use for testing, simply because it's the most buggy.

Tim Scarfe
05-11-2003, 01:56 PM
Only early releases of W2K out of the box comes with the offending browser. I'm pretty sure they re-distributed it.

I actually think that this is only the case "out-of-the-box" for beta/RC versions on W2K.

tomandlis
04-23-2006, 12:28 PM
When using your example code 'timer in action' I ran into an unexpected result
after the 'ticker.tick(1);' line I put an alert (shown here):

window.onload = function(){
var ticker = new Ticker();
ticker.tick(1);
alert("done ticking");
}


The alert popped up immediately which I didn't expect. In fact, I would think that line would never get executed, i.e., the timer would just keep on ticking and execution would never return to where I called.

The timer works in this example, it ticks away as expected, but why does the alert fire?

tomandlis
04-23-2006, 01:32 PM
I guess I should add if this is by design is there a way to change it? Or, would I even want to do that, i.e., would it be stupid to do that?

KC-Luck
04-24-2006, 03:42 AM
Just wanted to share another nice idea:


Function.prototype.interval = function(msecs) {
var f = this, fn = function() {
if (f(++f.tickCount)) clearInterval(f.intervalId);
};
this.tickCount = 0;
this.intervalId = setInterval(fn, msecs);
return this;
}

and could be used in many different ways :)


<html>
<head>
<script type="text/javascript">

Function.prototype.interval = function(msecs) {
var f = this, fn = function() {
if (f(++f.tickCount)) clearInterval(f.intervalId);
};
this.tickCount = 0;
this.intervalId = setInterval(fn, msecs);
return this;
}

function echo(s) {
return document.getElementById("response").appendChild(
document.createElement("div")).appendChild(
document.createTextNode(s));
}

</script>
</head>
<body>
<div id="response"></div>
<script>

var inline = (function(count) {
echo("inline, with var: "+ count);
if (count) return true;
}).interval(100);

(function(count) {
echo("inline, no var: "+ count);
return count;
}).interval(100);

onload = function() {
(function(count) {
if (!this.textNode)
this.textNode = echo("");
this.textNode.nodeValue = "onload: "+ count;
if (count >= 100) {
this.textNode.nodeValue = "onload: ended @"+ count;
this.textNode = null;
return true;
}
}).interval(10);
}

</script>
</body>
</html>

tomandlis
04-24-2006, 11:25 AM
When using your example code 'timer in action' I ran into an unexpected result
after the 'ticker.tick(1);' line I put an alert (shown here):

window.onload = function(){
var ticker = new Ticker();
ticker.tick(1);
alert("done ticking");
}


The alert popped up immediately which I didn't expect. In fact, I would think that line would never get executed, i.e., the timer would just keep on ticking and execution would never return to where I called.

The timer works in this example, it ticks away as expected, but why does the alert fire?

OK, figured it out. This is by design of the settimeout/setinterval which just tell the window to execute a method/function at a time in the future w/o halting execution of the script.

Given that if you have a process like "do X"--wait--"do Y" you would have to: "do X", call timer with reference to "do Y" and then pick up processing again in "do Y". I originally tried "do X" and "do Y" with a wait inbetween them which didn't work because "do Y" was executed with no wait.

tomandlis
04-24-2006, 11:29 AM
KC-Luck, would you please contrast your code vs. the original timer code. Its not clear to me what the difference is. Both seem to rely on setinterval how is yours better?

KC-Luck
04-24-2006, 03:46 PM
never said better, just that it may be useful?

easier to wire up a consistent function interval/or timeout(s) if so choose.

can be applied to any function of choice that way
for quick/painless function/thread/timer looping.

also simple to bail out of the loop when a certain criteria has been met.
hence the return true would "cancelBubble/stopPropagation" so to speak.

realtimethrill
06-13-2006, 03:58 PM
Hi
I can't claim to understand much of this discussion, and have become rather lost! But, the one fundamental thing amongst all this I'd love to know is: is scope lost with setInterval(), or isn't it? There seem to be opposing views! Is is this browser/platform dependant?

What I mean is, I can't make
setInterval("afunction(this)", delay) work. But I'm unsure if it's because setInterval is a global function (and therefore runs in a global scope ?-and therefore doesn't know what 'this' is (?)) ...or because of some string/eval()-type issue.

Evidence -to me anyway -that scope is not lost, is that this works:

setInterval("some_code", this.property) which I assume indicates that setInterval() runs in the current execution context.

Perhaps I'm confused...
Thanks for any tips!

tomandlis
09-29-2009, 07:57 PM
this codes still works in ie6 and ie7, but in ie8 a issue has come up and it requires some code modifications. See http://base.thep.lu.se/ticket/1363 for a description of the "The object invoked has disconnected from its clients" issue. Would anybody like to fix it?

tomandlis
09-29-2009, 08:52 PM
btw, the code seems to work in ie8 on windows xp, but not on ie8 for windows 7



EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum