I’ve got another crazy-weird setTimeout/setInterval behavior that you may not know about. However, unlike my previous discovery, this one may actually be useful.
Observe the following, seemingly innocuous, code:
var count = 0; var interval = setInterval(function(off){ document.body.innerHTML += " " + off; if ( ++count == 10 ) clearInterval( interval ); }, 100);
In particular pay attention to the use of the first argument within the callback function. Running this code in any browser, but a Mozilla based one, will give you the expected “undefined undefined undefined …”.
However, running the code in Firefox gives you some interesting results – something like the following:
Results: 4 -8 -7 -3 6 1 -1 -3 0 0
What are these numbers? Simply, they represent the time, in milliseconds, the callback was called away from the desired callback rate. In the above results the first call was called at 104ms, the second 92ms later, the third 93ms later – and so on.
The immediate advantage to this is in the ability to create ultra-precise animations and renderings. Typically, in order to do this, you would have to keep running logs of the timer calls (restricted by the extra overhead of the JavaScript execution). Whereas with this extra offset information you can easily determine the exact timer differences, as specified by the browser.
I’m curious to see how this could be used. Timer quality analysis? Detecting simultaneous timer execution? Dunno – there’s definitely potential, though.
Kevin Weibell (April 10, 2008 at 1:46 am)
setTimeout works, too!
var count = 0;
var fn = function(off){
document.body.innerHTML += " " + off;
if ( ++count !== 10 )
setTimeout( fn, 100 );
};
fn();
–> undefined -6 -6 -6 -21 9 9 -6 9 -6
neat ;)
Andrea Giammarchi (April 10, 2008 at 2:18 am)
so at this point this should be the normalizer behaviour function for every browser
if(!/firefox\/3/i.test(navigator.userAgent))
(function(callback){
var slice = Array.prototype.slice;
window.setTimeout = callback(window.setTimeout,slice);
window.setInterval = callback(window.setInterval,slice);
})(function(setInterval, slice){
return function(callback, delay){
var args = slice.call(arguments, 2),
time = new Date;
return setInterval(function(){
callback.apply(this, args.length ? args : [new Date - time - delay]);
time = new Date;
}, delay);
}
});
isn’t it? :)
Andrea Giammarchi (April 10, 2008 at 2:20 am)
P.S. above code is designed specially for IE that has a lot of problems with redeclaring setInterval and setTimeout, maybe a Safari or Opera implementation, that accept extra arguments and do not have redeclaration problems, could be more simple.
Cheers
Adam (April 10, 2008 at 5:44 am)
Sounds like it could be used to detect system load, if my understanding of the queuing process for timeout execution is correct.
It could be an interesting way to tailor your experience for the users available resources.
Arthur Debert (April 10, 2008 at 7:33 am)
Hi John.
We’ve been using this long ago in animation engines in the Actionscript world[1]. The runtime just can’t provide a precise callback interval, and keeping the render updates as close to the actual time as possible results in muchm, much better renderings.
Cheers
Arthur Debert
[1] http://code.google.com/p/tweener/
timeless (April 10, 2008 at 8:28 am)
please do not sniff for Gecko that way.
[0] http://geckoisgecko.org/
timeless (April 10, 2008 at 8:30 am)
Perhaps I should be clearer, this isn’t even a Gecko feature, it’s a Netscape legacy. So even choosing to sniff for Gecko is wrong, as you should also treat Netscape 4 (and iirc 2) the same way.
Andrea Giammarchi (April 10, 2008 at 8:58 am)
timeless, so I suppose this is not a FF3 feature, that is basically the reason I choosed to sniff in that way.
if(!/\bGecko\b/i.test(navigator.userAgent))
... stuff ...
better? :)
Benjamin Stover (April 10, 2008 at 10:41 am)
What I find interesting: if the pattern really was “104 92 …”, that would mean the second callback was executed 4 milliseconds before it was supposed to! I had assumed the callback was always executed at or after the given delay.
Boris (April 10, 2008 at 11:46 am)
Benjamin, there is some logic that uses the lateness of previous callbacks to try to adjust later ones. Sometimes it overcompensates…
Erik Arvidsson (April 10, 2008 at 12:22 pm)
One thing to remember is that if you use partial, bind, etc you need to make sure to remember that mozilla includes this extra parameter.
Andrea: WebKit has “like Gecko” in it’s user agent.
var isGecko = navigator.product == 'Gecko' && !/webkit/i.test(navigator.userAgent);
Andrea Giammarchi (April 10, 2008 at 1:08 pm)
ok, I’ve wrote that code 5 minutes after I woke up this morning (too geek?) … sorry if I did not test every browser :D
sniff as you want, the rest works fine for me … don’t it?
Michael Jackson (April 11, 2008 at 10:40 am)
@John: Cool discovery. However, I think it would be easier to just check the current time in your callback function and determine the true interval based on your initial starting time (and, in the case of setInterval, the number of frames already rendered). That saves the need to try and make this cross-browser with Andrea’s fix…and it saves lines of code.
Hector (April 14, 2008 at 7:29 am)
John, every since I first played with jQuery and we had our discussions around timers and AJAX, I think you continue to not heed the advice of experts who are highly experience in this area. You are breaking the #1 rule of thumb known by the experts and written in all all process control/multi-thread design books – NEVER, ABSOLUTELY NEVER based your application designs around TIMERS especially something like this where you got TRUE unknowns on how the multi-tasker simulator langugage RTE is managing event timers. I’ve seen you explore the timer issues, and its all DEJA VU. Many people like yourself, including myself during 20s, have done the same thing trying to make sense of it. You can’t and by that I mean, it is why you will never get a consistent behavior and have you noticed you haven’t gotten persistent timing behavior between all the different browsers and OSes.
The #1 mistake is to design something around the 13 or 15 ms delay. Something that can CHANGE tomorrow.
Until you add true pre-emptive, context switching stack and code management and/or multi-thread design methods into javascript, you will always have these multi-tasking related timing iussues.
Consider this, the more you add complexity to interactive application design via BROWSERS, the more the incentive it becomes to go NATIVE.
—
Dao (April 26, 2008 at 5:49 am)
Very useful indeed!
https://bugzilla.mozilla.org/show_bug.cgi?id=430925
https://bugzilla.mozilla.org/show_bug.cgi?id=355965
I hope we won’t lose this quirk with the ongoing JS engine work.
Henrik (April 28, 2008 at 1:01 pm)
John, this remembers me bug 299476 I filed nearly 3 years ago:
https://bugzilla.mozilla.org/show_bug.cgi?id=299476
There were some troubles using a callback function with arguments the way you are describing here. Was there a change somewhere in the past which changed this behavior?