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?
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. ...
You could consider using the html5 webaudio clock which uses the system time for better accuracy
Here's an example demoing Shog9's suggestion. This fills a jquery progress bar smoothly over 6 seconds, then redirects to a different page once it's filled:
I had a similar problem not long ago and came up with an approach which combines
requestAnimationFrame
with performance.now() which works very effectively.Im now able to make timers accurate to approx 12 decimal places:
http://jsfiddle.net/CGWGreen/9pg9L/
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:
But your while loop can still get interrupted by other processes so it's still not perfect.
If you're using
setTimeout()
to yield quickly to the browser so it's UI thread can catch up with any tasks it needs to do (such as updating a tab, or to not show the Long Running Script dialog), there is a new API called Efficient Script Yielding, aka,setImmediate()
that may work a bit better for you.setImmediate()
operates very similarly tosetTimeout()
, yet it may run immediately if the browser has nothing else to do. In many situations where you are usingsetTimeout(..., 16)
orsetTimeout(..., 4)
orsetTimeout(..., 0)
(i.e. you want the browser to run any outstanding UI thread tasks and not show a Long Running Script dialog), you can simply replace yoursetTimeout()
withsetImmediate()
, dropping the second (millisecond) argument.The difference with
setImmediate()
is that it is basically a yield; if the browser has sometime to do on the UI thread (e.g., update a tab), it will do so before returning to your callback. However, if the browser is already all caught up with its work, the callback specified insetImmediate()
will essentially run without delay.Unfortunately it is only currently supported in IE9+, as there is some push back from the other browser vendors.
There is a good polyfill available though, if you want to use it and hope the other browsers implement it at some point.
If you are using
setTimeout()
for animation, requestAnimationFrame is your best bet as your code will run in-sync with the monitor's refresh rate.If you are using
setTimeout()
on a slower cadence, e.g. once every 300 milliseconds, you could use a solution similar to what user1213320 suggests, where you monitor how long it was from the last timestamp your timer ran and compensate for any delay. One improvement is that you could use the new High Resolution Time interface (akawindow.performance.now()
) instead ofDate.now()
to get greater-than-millisecond resolution for the current time.