views:

1495

answers:

8

Something that has always bugged me is how unpredictable the setTimeout() method in Javascript is.

In my experience, the timer is horribly inaccurate in a lot of situations. By inaccurate, I mean the actual delay time seems to vary by 250-500ms more or less. Although this isn't a huge amount of time, when using it to hide/show UI elements the time can be visibly noticeable.

Are there any tricks that can be done to ensure that setTimeout() performs accurately (without resorting to an external API) or is this a lost cause?

A: 

Hate to say it, but I don't think there is a way to alleviate this. I do think that it depends on the client system, though, so a faster javascript engine or machine may make it slightly more accurate.

Eric Wendelin
I have a dual core 2.8 Ghz machine and the timers are still pretty inaccurate. In my experience, CPU speed doesn't matter very much.
Dan Herbert
A: 

To my experience it is lost effort, even as the smallest reasonable amount of time I ever recognized js act in is around 32-33 ms. ...

roenving
A: 

There is definitely a limitation here. To give you some perspective, the Chrome browser Google just released is fast enough that it can execute setTimeout(function() {}, 0) in 15-20 ms whereas older Javascript engines took hundreds of milliseconds to execute that function. Although setTimeout uses milliseconds, no javascript virtual machine at this point in time can execute code with that precision.

Bialecki
I heard Chrome is the first browser to be that accurate, however I still haven't been able to test that.
Dan Herbert
You're a little bit off: 15ms is the standard timer granularity on Windows, and therefore the granularity for timers in most browsers *running* on Windows. Chrome is apparently using more accurate hardware timers to provide better granularity. JS execution speed is a separate concern.
Shog9
Chrome has got bugs for this -- increasing the setTimeout resolution breaks certain websites that assume 15ms resolution, and destroys battery life on windows (windows architecture apparently fights decent resolution timers)
olliej
A: 

Dan, from my experience (that includes implementation of SMIL2.1 language in JavaScript, where time management is in subject) I can assure you that you actually never need high precision of setTimeout or setInterval.

What does however matter is the order in which setTimeout/setInterval gets executed when queued - and that always works perfectly.

Sergey Ilinsky
While I never really NEED high precision timing, it can be bothersome when the delay I set for something UI dependent is slightly off. It doesn't break anything, but it can be noticed since it is sort of inconsistent.
Dan Herbert
Shog9 provided full insight into what I wrapped with simple statements. And yes, it is not "browser" or "javascript implementation" that cannot ensure timing, it is the OS, people, world etc. Thus rather rely on "happening" then on "timing".
Sergey Ilinsky
+12  A: 

Are there any tricks that can be done to ensure that setTimeout() performs accurately (without resorting to an external API) or is this a lost cause?

No and no. You're not going to get anything close to a perfectly accurate timer with setTimeout() - browsers aren't set up for that. However, you don't need to rely on it for timing things either. Most animation libraries figured this out years ago: you set up a callback with setTimeout(), but determine what needs to be done based on the value of (new Date()).milliseconds (or equivalent). This allows you to take advantage of more reliable timer support in newer browsers, while still behaving appropriately on older browsers.

It also allows you to avoid using too many timers! This is important: each timer is a callback. Each callback executes JS code. While JS code is executing, browser events - including other callbacks - are delayed or dropped. When the callback finishes, additional callbacks must compete with other browser events for a chance to execute. Therefore, one timer that handles all pending tasks for that interval will perform better than two timers with coinciding intervals, and (for short timeouts) better than two timers with overlapping timeouts!

Summary: stop using setTimeout() to implement "one timer / one task" designs, and use the real-time clock to smooth out UI actions.

Shog9
+1  A: 

JavaScript timeouts have a defacto limit of 10-15ms (I'm not sure what you're doing to get 200ms, unless you're doing 185ms of actual js execution). This is due to windows having a standard timer resolution of 15ms, the only way to do better is to use Windows' higher resolution timers which is a system wide setting so can screw with other applications on the system and also chews battery life (Chrome has a bug from Intel on this issue).

The defacto standard of 10-15ms is due to people using 0ms timeouts on websites but then coding in a way that assumes that assumes a 10-15ms timeout (eg. js games which assume 60fps but ask 0ms/frame with no delta logic so the game/site/animation goes a few orders of magnitude faster than intended). To account for that, even on platforms that don't have windows' timer problems, the browsers limit timer resolution to 10ms.

olliej
Just to be clear, Chrome actually honors 0ms timeouts (rather, executes them at the most immediate point it can).
eyelidlessness
+2  A: 

shog9's answer is pretty much what I'd say, although I'd add the following about UI animation/events:

If you've got a box that's supposed to slide onto the screen, expand downwards, then fade in its contents, don't try to make all three events separate with delays timed to make them fire one after another - use callbacks, so once the first event is done sliding it calls the expander, once that's done it calls the fader. jQuery can do it easily, and I'm sure other libraries can as well.

matt lohkamp
A: 

You need to "creep up" on the target time. Some trial and error will be necessary but in essence.

Set a timeout to complete arround 100ms before the required time

make the timeout handler function like this:

calculate_remaining_time
if remaining_time > 20ms // maybe as much as 50
  re-queue the handler for 10ms time
else
{
  while( remaining_time > 0 ) calculate_remaining_time;
  do_your_thing();
  re-queue the handler for 100ms before the next required time
}

But your while loop can still get interrupted by other processes so it's still not perfect.

Noel Walters
This isn't a terrible idea, assuming you need to hit an exact wall-clock time *on the dot*. But if you're just after a consistent interval, you can achieve similar results by simply adjusting the timeout value on the fly to something just shy of the interval period less the time taken by your processing. And even for something like a reminder or ticking clock, 15ms accuracy is probably good enough in most cases.
Shog9
The only time I've needed such accuracy was trying to implement a musical beat. But Javascript timers are not really suitable for the job. Even with this design the ticking is quite erratic and not really suitable for a metronome.
Noel Walters