Есть ли более точный способ создания таймера Javascript, чем setTimeout? - PullRequest
66 голосов
/ 13 октября 2008

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

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

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

Ответы [ 16 ]

72 голосов
/ 13 октября 2008

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

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

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

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

32 голосов
/ 06 марта 2012

.

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

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

10 голосов
/ 19 апреля 2013

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

Теперь я могу делать таймеры с точностью до 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 голосов
/ 01 сентября 2011

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

https://gist.github.com/1185904

4 голосов
/ 13 октября 2008

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

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

3 голосов
/ 14 января 2014

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

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

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

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

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

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

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

2 голосов
/ 08 марта 2013

Вот пример, демонстрирующий предложение 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);
    }                 
}
2 голосов
/ 06 сентября 2010

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

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

1 голос
/ 04 декабря 2014

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

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}
0 голосов
/ 29 сентября 2017

Вот что я использую. Поскольку это 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')
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...