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

Что-то, что меня всегда раздражало, - это непредсказуемый метод setTimeout() в Javascript.

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

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

Ответ 1

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

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

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

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

Ответ 2

.

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%.

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

Ответ 3

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

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/

Ответ 4

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

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

Ответ 5

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

https://gist.github.com/1185904

Ответ 6

Если вы используете setTimeout(), чтобы быстро перейти в браузер, чтобы поток пользовательского интерфейса мог догнать любые задачи, которые он должен выполнить (например, обновить вкладку или не показывать Long Running Script), существует новый API под названием Эффективный Script Yielding, aka, setImmediate(), который может работать немного лучше для вас.

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

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

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

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

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

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

Ответ 7

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

Установите время ожидания для завершения arround 100ms до требуемого времени

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

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 может прерваться другими процессами, поэтому он все еще не идеален.

Ответ 8

Вот пример демонстрации предложения 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);
    }                 
}

Ответ 9

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

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}

Ответ 10

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

Ответ 11

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

Ответ 12

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

Ответ 13

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

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

Ответ 14

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

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

Ответ 15

Вот что я использую. Поскольку это JavaScript, я опубликую как мои решения Frontend, так и node.js:

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

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

node.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')

Ответ 16

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