UPDATE на студии Android AVD:
Нет микроволн или лифтов:)
Почему это неточно?
Поскольку вы используете
setTimeout()
илиsetInterval()
. Им нельзя доверять , для них нет гарантий точности. Им разрешено произвольно запаздывать , и они не сохраняют постоянный темп, но имеют тенденцию дрейфовать (как вы заметили).Как создать точный таймер?
Вместо этого используйте объект
Date
, чтобы получить точное (текущее) время (миллисекунда). Затем укажите свою логику на текущее значение времени, вместо того, чтобы подсчитывать, как часто выполнялся ваш обратный вызов.Для простого таймера или часов четко отслеживайте разницу во времени:
var start = Date.now(); setInterval(function() { var delta = Date.now() - start; // milliseconds elapsed since start … output(Math.floor(delta / 1000)); // in seconds // alternatively just show wall clock time: output(new Date().toUTCString()); }, 1000); // update about every second
Теперь у этого есть проблема, возможно, прыгающих значений. Когда интервал задерживается бит и выполняет ваш обратный вызов после
990
,1993
,2996
,3999
,5002
миллисекунд, вы увидите второй счет0
,1
,2
,3
,5
(!). Поэтому было бы желательно обновлять чаще, примерно каждые 100 мс, чтобы избежать таких переходов.Однако иногда вам действительно нужен постоянный интервал, выполняющий ваши обратные вызовы без дрейфа. Для этого требуется немного более выгодная стратегия (и код), хотя она хорошо платит (и регистрирует меньше тайм-аутов). Они известны как самонастраивающиеся таймеры. Здесь точная задержка для каждого из повторных тайм-аутов адаптируется к фактически прошедшему времени по сравнению с ожидаемыми интервалами:
var interval = 1000; // ms var expected = Date.now() + interval; setTimeout(step, interval); function step() { var dt = Date.now() - expected; // the drift (positive for overshooting) if (dt > interval) { // something really bad happened. Maybe the browser (tab) was inactive? // possibly special handling to avoid futile "catch up" run } … // do what is to be done expected += interval; setTimeout(step, Math.max(0, interval - dt)); // take into account drift }
Я согласен с Берги в использовании Date, но его решение было немного излишним для моего использования. Я просто хотел, чтобы мои анимированные часы (цифровые и аналоговые SVG) обновлялись на втором и не переполнялись или выполнялись, создавая очевидные скачки в обновлениях часов. Вот фрагмент кода, который я ввел в мои функции обновления часов:
var milliseconds = now.getMilliseconds();
var newTimeout = 1000 - milliseconds;
this.timeoutVariable = setTimeout((function(thisObj) { return function() { thisObj.update(); } })(this), newTimeout);
Он просто вычисляет время дельта до следующего даже второго и устанавливает тайм-аут этой дельта. Это синхронизирует все мои объекты часов со вторым. Надеюсь, это полезно.
Я просто опишу ответ Берги (в частности, вторую часть) немного, потому что мне очень понравилось, как это было сделано, но я хочу, чтобы опция останавливала таймер после его запуска ( как clearInterval()
почти). Sooo ... Я завернул его в конструктор, чтобы мы могли делать с ним «объективные» вещи.
Хорошо, поэтому вы копируете / вставляете это ...
/**
* Self-adjusting interval to account for drifting
*
* @param {function} workFunc Callback containing the work to be done
* for each interval
* @param {int} interval Interval speed (in milliseconds) - This
* @param {function} errorFunc (Optional) Callback to run if the drift
* exceeds interval
*/
function AdjustingInterval(workFunc, interval, errorFunc) {
var that = this;
var expected, timeout;
this.interval = interval;
this.start = function() {
expected = Date.now() + this.interval;
timeout = setTimeout(step, this.interval);
}
this.stop = function() {
clearTimeout(timeout);
}
function step() {
var drift = Date.now() - expected;
if (drift > that.interval) {
// You could have some default stuff here too...
if (errorFunc) errorFunc();
}
workFunc();
expected += that.interval;
timeout = setTimeout(step, Math.max(0, that.interval-drift));
}
}
Скажите, что делать и все это ...
// For testing purposes, we'll just increment
// this and send it out to the console.
var justSomeNumber = 0;
// Define the work to be done
var doWork = function() {
console.log(++justSomeNumber);
};
// Define what to do if something goes wrong
var doError = function() {
console.warn('The drift exceeded the interval.');
};
// (The third argument is optional)
var ticker = new AdjustingInterval(doWork, 1000, doError);
// You can start or stop your timer at will
ticker.start();
ticker.stop();
// You can also change the interval while it's in progress
ticker.interval = 99;
Я имею в виду, это работает для меня в любом случае. Если есть лучший способ, знаете ли.
clearTimeout
на последнем.
– Bergi
2 June 2017 в 22:26
Не намного точнее, чем это.
var seconds = new Date().getTime(), last = seconds,
intrvl = setInterval(function() {
var now = new Date().getTime();
if(now - last > 5){
if(confirm("Delay registered, terminate?")){
clearInterval(intrvl);
return;
}
}
last = now;
timer.innerHTML = now - seconds;
}, 333);
Что касается того, почему это не точно, я бы предположил, что машина занята другими делами, немного замедляя каждую итерацию добавляет, как вы видите.
step
несколько раз подряд. Если вы используете стратегию разницы (вместо счетчика), которую вы должны независимо от того, настроен ли таймер самостоятельно или нет, вам потребуется всего один шаг, и он автоматически набирается. – Bergi 3 March 2016 в 10:03