Есть ли более точный способ создать таймер Javascript, чем setTimeout?


то, что всегда меня беспокоило, насколько непредсказуемо setTimeout() метод в Javascript есть.

по моему опыту, таймер ужасно неточен во многих ситуациях. Под неточным я подразумеваю, что фактическое время задержки, похоже, изменяется на 250-500 мс более или менее. Хотя это не огромное количество времени, при использовании его для скрытия/отображения элементов пользовательского интерфейса время может быть заметно заметно.

есть ли какие-либо трюки, которые можно сделать, чтобы гарантировать, что setTimeout() выполняет точно (не прибегая к внешнему API) или это гиблое дело?

16 64

16 ответов:

есть ли какие-либо трюки, которые можно сделать чтобы убедиться, что setTimeout() выполняет точно (не прибегая к внешний API) или это гиблое дело?

нет и нет. Вы не получите ничего близкого к совершенно точному таймеру с setTimeout() - браузеры не настроены на это. , вам не нужно полагаться на него для синхронизации вещей либо. Большинство библиотек анимации поняли это много лет назад: вы настроили обратный вызов с помощью setTimeout(), но определить, что должно быть сделано на основе значения (new Date()).milliseconds (или эквивалент). Это позволяет вам воспользоваться более надежной поддержкой таймера в новых браузерах, в то же время ведя себя соответствующим образом в старых браузерах.

он также позволяет избегайте использования слишком много таймеров! Это важно: каждый таймер является обратным вызовом. Каждый обратный вызов выполняет JS-код. Во время выполнения кода JS события браузера, включая другие обратные вызовы, задерживаются или удаляются. Когда обратный вызов заканчивается, дополнительные обратные вызовы должны конкурировать с другими событиями браузер для выполнения. Поэтому один таймер, который обрабатывает все отложенные задачи для этого интервала, будет работать лучше, чем два таймера с совпадающими интервалами, и (для коротких тайм-аутов) лучше, чем два таймера с перекрывающимися тайм-аутами!

резюме: прекратите использовать setTimeout() для реализации проектов "один таймер / одна задача" и использования часов реального времени для сглаживания действий пользовательского интерфейса.

.

REF;http://www.sitepoint.com/creating-accurate-timers-in-javascript/

этот сайт выручил меня в крупном масштабе.

вы можете использовать системные часы для компенсации погрешности таймера. Если вы запускаете функцию синхронизации как серию вызовов setTimeout - каждый экземпляр вызывает следующий - то все, что вам нужно сделать, чтобы сохранить его точным, это точно определить, насколько он неточен, и вычесть эту разницу из следующего итерация:

var start = new Date().getTime(),  
    time = 0,  
    elapsed = '0.0';  
function instance()  
{  
    time += 100;  
    elapsed = Math.floor(time / 100) / 10;  
    if(Math.round(elapsed) == elapsed) { elapsed += '.0'; }  
    document.title = elapsed;  
    var diff = (new Date().getTime() - start) - time;  
    window.setTimeout(instance, (100 - diff));  
}  
window.setTimeout(instance, 100);  

этот метод минимизирует дрейф и уменьшает неточности более чем на 90%.

Он исправил мои проблемы, надеюсь, что это поможет

у меня была подобная проблема не так давно и придумал подход, который сочетает в себе requestAnimationFrame с производительностью.Теперь() который работает очень эффективно.

Im теперь в состоянии сделать таймеры с точностью до приблизительно 12 десятичных знаков:

    window.performance = window.performance || {};
    performance.now = (function() {
        return performance.now       ||
            performance.mozNow    ||
            performance.msNow     ||
            performance.oNow      ||
            performance.webkitNow ||
                function() {
                    //Doh! Crap browser!
                    return new Date().getTime(); 
                };
        })();

http://jsfiddle.net/CGWGreen/9pg9L/

ответ shog9-это в значительной степени то, что я бы сказал, хотя я бы добавил следующее об анимации/событиях пользовательского интерфейса:

Если у вас есть окно, которое должно скользить по экрану, расширяться вниз, а затем исчезать в его содержимом, не пытайтесь разделить все три события с задержками, приуроченными к тому, чтобы они срабатывали один за другим - используйте обратные вызовы, поэтому, как только первое событие сделано, оно вызывает расширитель, как только это сделано, он вызывает фейдер. jQuery может сделать это легко, и я уверен, что другие библиотеки тоже могут.

Если вам нужно получить точный обратный вызов на заданном интервале, это может помочь вам:

https://gist.github.com/1185904

если вы используете setTimeout() быстро уступить браузеру таким образом, это поток пользовательского интерфейса может догнать любые задачи, которые ему нужно сделать (например, обновить вкладку или не показывать длинный диалог сценария), есть новый API под названием Эффективный Скрипт Дает, он же, setImmediate() что может работать немного лучше для вас.

setImmediate() работает аналогично setTimeout(), но он может работать немедленно, если браузер не имеет ничего другого, чтобы сделать. Во многих ситуациях, когда вы используете setTimeout(..., 16) или setTimeout(..., 4) или setTimeout(..., 0) (т. е. вы хотите, чтобы браузер запускал любые выдающиеся задачи потока пользовательского интерфейса и не показывал длинный диалог сценария), вы можете просто заменить свой setTimeout() с setImmediate(), роняя секунды (миллисекунды) аргументы.

разница с setImmediate() это в основном выход; если браузер должен когда-нибудь сделать в потоке пользовательского интерфейса (например, обновить вкладку), он сделает это перед возвращением к вашему обратному вызову. Однако, если в браузере уже все догнав его работу, обратный вызов указан в setImmediate() будет по существу работать без задержки.

к сожалению, в настоящее время он поддерживается только в IE9+, так как есть некоторые отодвинуть от других поставщиков браузеров.

есть хороший полифилл хотя, если вы хотите использовать его и надеюсь, что в других браузерах это осуществить в какой-то момент.

если вы используете setTimeout() анимации, requestAnimationFrame это ваш лучший ставку, как ваш код будет работать в синхронизации с частотой обновления монитора.

если вы используете setTimeout() на более медленной каденции, например, раз в 300 миллисекунд, вы можете использовать решение, подобное тому, что предлагает user1213320, где вы отслеживаете, как долго он был от последней отметки времени, когда ваш таймер работал, и компенсировать любую задержку. Одним из улучшений является то, что вы можете использовать новый Время Высокого Разрешения интерфейс (он же window.performance.now()) вместо Date.now() чтобы получить разрешение больше миллисекунды для текущего времени.

вам нужно "ползти вверх" на целевое время. Некоторые проб и ошибок будет необходимо, но по существу.

установите тайм-аут для завершения около 100 мс до требуемого времени

сделать функцию обработчика таймаута следующим образом:

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
}

но ваш цикл while все еще может быть прерван другими процессами, поэтому он все еще не идеален.

вот пример демонстрации предложения Shog9. Это заполняет индикатор выполнения jquery плавно в течение 6 секунд, а затем перенаправляет на другую страницу после ее заполнения:

var TOTAL_SEC = 6;
var FRAMES_PER_SEC = 60;
var percent = 0;
var startTime = new Date().getTime();

setTimeout(updateProgress, 1000 / FRAMES_PER_SEC);

function updateProgress() {
    var currentTime = new Date().getTime();

    // 1000 to convert to milliseconds, and 100 to convert to percentage
    percent = (currentTime - startTime) / (TOTAL_SEC * 1000) * 100;

    $("#progressbar").progressbar({ value: percent });

    if (percent >= 100) {
        window.location = "newLocation.html";
    } else {
        setTimeout(updateProgress, 1000 / FRAMES_PER_SEC);
    }                 
}

это таймер, который я сделал для моего музыкального проекта, который делает эту вещь. Таймер, который является точным на всех устройствах.

var Timer = function(){
  var framebuffer = 0,
  var msSinceInitialized = 0,
  var timer = this;

  var timeAtLastInterval = new Date().getTime();

  setInterval(function(){
    var frametime = new Date().getTime();
    var timeElapsed = frametime - timeAtLastInterval;
    msSinceInitialized += timeElapsed;
    timeAtLastInterval = frametime;
  },1);

  this.setInterval = function(callback,timeout,arguments) {
    var timeStarted = msSinceInitialized;
    var interval = setInterval(function(){
      var totaltimepassed = msSinceInitialized - timeStarted;
      if (totaltimepassed >= timeout) {
        callback(arguments);
        timeStarted = msSinceInitialized;
      }
    },1);

    return interval;
  }
}

var timer = new Timer();
timer.setInterval(function(){console.log("This timer will not drift."),1000}

ненавижу это говорить, но я не думаю, что есть способ облегчить это. Я думаю, что это зависит от клиентской системы, хотя, так что более быстрый движок javascript или машина может сделать это немного более точная.

по моему опыту это потерянное усилие, даже если наименьшее разумное количество времени, которое я когда-либо признавал JS act, составляет около 32-33 МС. ...

здесь определенно есть ограничение. Чтобы дать вам некоторую перспективу, браузер Chrome, только что выпущенный Google, достаточно быстр, чтобы он мог выполнять setTimeout(function () {}, 0) за 15-20 мс, тогда как более старые двигатели Javascript занимали сотни миллисекунд для выполнения этой функции. Хотя setTimeout использует миллисекунды, ни одна виртуальная машина javascript на данный момент времени не может выполнить код с такой точностью.

Дэн, из моего опыта (который включает в себя реализацию языка SMIL2.1 в JavaScript, где управление временем находится в теме) я могу заверить вас, что вам на самом деле никогда не нужна высокая точность setTimeout или setInterval.

однако имеет значение порядок, в котором setTimeout/setInterval выполняется при постановке в очередь - и это всегда работает отлично.

таймауты JavaScript имеют ограничение defacto 10-15ms (я не уверен, что вы делаете, чтобы получить 200 мс, если вы не делаете 185 МС фактического выполнения js). Это связано с тем, что windows имеет стандартное разрешение таймера 15 мс, единственный способ сделать лучше-использовать таймеры с более высоким разрешением Windows, что является общесистемной настройкой, поэтому можно завинчивать другие приложения в системе, а также жевать время автономной работы (у Chrome есть ошибка от Intel по этому вопросу).

стандарт дефакто 10-15ms связано с тем, что люди используют тайм-ауты 0ms на веб-сайтах, но затем кодируют таким образом, что предполагает, что предполагает тайм-аут 10-15ms (например. js-игры, которые предполагают 60fps, но задают 0ms/frame без Дельта-логики, поэтому игра/сайт/анимация идет на несколько порядков быстрее, чем предполагалось). Чтобы учесть это, даже на платформах, которые не имеют проблем с таймером windows, браузеры ограничивают разрешение таймера до 10 мс.

вот что я использую. Поскольку это JavaScript, я опубликую как свой интерфейс, так и узел.js solutions:

для обоих я использую ту же функцию десятичного округления, которую я настоятельно рекомендую вам держать на расстоянии вытянутой руки, потому что причины:

const round = (places, number) => +(Math.round(number + `e+${places}`) + `e-${places}`)

places - количество десятичных знаков, при которых округляется, это должно быть безопасно и должно избегать любых проблем с поплавками (некоторые числа, такие как 1.0000000000005~, могут быть проблематичными). Я потратил время на изучение лучшего способа округления десятичных знаков предоставляются таймеры высокого разрешения, преобразованные в миллисекунды.

that + symbol - это унарный оператор, который преобразует операнд в число, практически идентичное Number()

Обозреватель

const start = performance.now()

// I wonder how long this comment takes to parse

const end = performance.now()

const result = (end - start) + ' ms'

const adjusted = round(2, result) // see above rounding function

узел.js

// Start timer
const startTimer = () => process.hrtime()

// End timer
const endTimer = (time) => {
    const diff = process.hrtime(time)
    const NS_PER_SEC = 1e9
    const result = (diff[0] * NS_PER_SEC + diff[1])
    const elapsed = Math.round((result * 0.0000010))
    return elapsed
}

// This end timer converts the number from nanoseconds into milliseconds;
// you can find the nanosecond version if you need some seriously high-resolution timers.

const start = startTimer()

// I wonder how long this comment takes to parse

const end = endTimer(start)

console.log(end + ' ms')

вы могли бы рассмотреть с помощью html5 webaudio clock, который использует системное время для большей точности