I have encountered following issue when creating simple task: displaying html clock by using WebKit engine. Additional requirement were to handle system time change and that it should work on Windows. I have used setInterval to achive this but it seems to freeze browser after I change system time backward. For me it looks like WebKit issue. It is easy to reproduce on safari by running this simple code:
<p id="date"></p>
setInterval(SetTime, 1000);
function SetTime() {
document.getElementById('date').textContent=new Date();
}
After that I have made another approach with recursive setTimeout call. Same effect.
(function loop() {
document.getElementById('date').textContent=new Date();
setTimeout(loop, 1000);
})();
Any ideas why is that happening and how to go around this?
Why are setTimeout and setInterval not accurate? To answer this question, you need to understand that there is a mechanism called event loop in the JavaScript host environment (browser or Node. js). It is necessary for front-end developers to understand this mechanism.
This is unlikely to make much of a difference though, and as has been mentioned, using setInterval with long intervals (a second is big, 4ms is small) is unlikely to have any major effects.
Method 1: Calling the function once before executing setInterval: The function can simply be invoked once before using the setInterval function. This will execute the function once immediately and then the setInterval() function can be set with the required callback.
To mitigate the potential impact this can have on performance, once intervals are nested beyond five levels deep, the browser will automatically enforce a 4 ms minimum value for the interval. Attempts to specify a value less than 4 ms in deeply-nested calls to setInterval() will be pinned to 4 ms.
This is almost definitely an issue with WebKit.
When you use setTimeout
, you create a 'timer' with two properties:
You can imagine a naïve implementation of setTimeout
looking something like this:
var timers = [];
function setTimeout(callback, delay) {
var id = timers.length;
timers[id] = {
callback: callback,
timestamp: Date.now() + delay
}
return id;
}
This would simply create a timer and add it to a list. Then, on each tick, the JS runtime would check these timers and execute the callbacks for those that have fired:
var now = Date.now();
for(var id in timers) {
var timer = timers[id];
if(timer && timer.timestamp < now) {
callback();
delete timers[id];
}
}
Given this implementation, imagine now changing the system time (i.e. Date.now()
in the examples above) to a value in the past -- the timer's timestamp will still be set relative to the previous system time (i.e. in the future).
The same issue exists with setInterval
, which (assuming sane code in WebKit) will be implemented using setTimeout
.
Unfortunately, this means that any invocation of setTimeout
or setInterval
is going to suffer.
As an alternative, you can use the lovely window.requestAnimationFrame
method to perform a callback on each tick. I haven't tested this at at all, but it should continue to fire on each tick, regardless of the system time.
As a bonus, each time it fires the callback, you get passed the current timestamp as a parameter. By keeping track of the previous timestamp passed to your callback, you could easily detect backwards system time changes:
var lastTimestamp;
var onNextFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame;
var checkForPast = function(timestamp) {
if(lastTimestamp && timestamp < lastTimestamp) {
console.error('System time moved into the past! I should probably handle this');
}
lastTimestamp = timestamp;
onNextFrame(checkForPast);
};
onNextFrame(checkForPast);
This might not be great news for you, but you should probably rewrite your entire application to use requestAnimationFrame
anyway - it seems much more suited to your needs:
var onNextFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame;
var dateElem = document.getElementById('date');
var updateDate = function(timestamp) {
dateElem.textContent = new Date();
onNextFrame(updateDate);
};
onNextFrame(updateDate);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With