2008-10-12 3 views
61

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

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

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

ответ

69

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

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

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

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

+58

Было бы здорово увидеть пример этого. – backdesk 2012-03-14 15:45:22

+0

Как насчет вызова setTimeout() от рабочего. Разве это не уменьшит блокировку, вызванную рендерингом? – hidarikani 2014-11-17 15:49:19

+0

Нет никакой гарантии, что @hidarikani. FWIW, это может быть такой же проблемой в собственном коде, и в браузере у вас нет возможности требовать таймеры с высоким разрешением и задержки в реальном времени. Не то, чтобы вы хотели; это рецепт для потери CPU, и вы должны предположить, что, по крайней мере, в некоторых случаях браузер и ОС будут делать все возможное, чтобы сохранить время автономной работы и принести в жертву вашу точность таймера для этой цели. – Shog9 2014-11-17 20:31:13

0

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

+0

У меня есть двухъядерный 2,8 ГГц машина, и таймеры все еще довольно неточны. По моему опыту, скорость процессора не имеет большого значения. – 2008-10-12 21:24:37

0

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

0

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

+0

Я слышал, что Chrome является первым браузером, который будет точным, однако я все еще не смог его проверить. – 2008-10-12 20:54:20

+2

Вы немного не в состоянии: 15 мс - стандартная таймерность в Windows, и, следовательно, гранулярность для таймеров в большинстве браузеров * работает * в Windows. Chrome, по-видимому, использует более точные аппаратные таймеры для обеспечения лучшей детализации. Скорость выполнения JS - отдельная проблема. – Shog9 2008-10-12 21:50:25

+0

У Chrome есть ошибки для этого: увеличение разрешения setTimeout ломает определенные веб-сайты, которые предполагают разрешение 15 мс, и разрушает время автономной работы на окнах (архитектура окон, по-видимому, борется с приличными таймерами разрешения) – olliej 2008-10-12 22:16:01

0

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

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

+0

Хотя мне никогда не нужна высокая точность синхронизации, это может быть назойливым когда задержка, установленная для чего-то зависимого от пользовательского интерфейса, немного отключена. Это ничего не сломает, но это можно заметить, поскольку это своего рода несогласованность. – 2008-10-12 21:27:27

+0

Shog9 предоставил полное представление о том, что я завернул простыми заявлениями. И да, это не «браузер» или «реализация javascript», которые не могут обеспечить синхронизацию, это ОС, люди, мир и т. Д. Таким образом, скорее полагайтесь на «происходящее», а затем на «время». – 2008-10-12 21:52:58

0

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

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

4

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

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

2

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

Установите тайм-аут, чтобы завершить Arround 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 
} 

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

4

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

https://gist.github.com/1185904

32

.

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

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

2

Вот пример записи демо предложение 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

У меня была аналогичная проблема, не так давно и придумали подход, который сочетает в себе 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/

3

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

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

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

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

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

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

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

1

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

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}

-1

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

0

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

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

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

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

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

Browser

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') 
Смежные вопросы