В чем причина того, что JavaScript setTimeout такой неточный?

Я получил этот код здесь:

var date = new Date();
setTimeout(function(e) {
    var currentDate = new Date();
    if(currentDate - date >= 1000) {
         console.log(currentDate, date);
         console.log(currentDate-date);
    }
    else {
       console.log("It was less than a second!");
       console.log(currentDate-date);
    }
}, 1000);

В моем компьютере это всегда выполняется правильно, с 1000 на выходе консоли. Заинтересован в другом компьютере, тот же код, время ожидания обратного вызова начинается менее чем за секунду, и разница currentDate - date между 980 и 998.

Я знаю о существовании библиотек, которые решают эту неточность (например, Tock).

В основном, мой вопрос: каковы причины, потому что setTimeout не срабатывает в заданную задержку? Может ли это быть компьютер, который работает слишком медленно, и браузер автоматически пытается адаптироваться к медленности и запускает событие раньше?

PS: Вот скриншот кода и результатов, выполненных в консоли Chrome JavaScript:

5 ответов

Решение

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

В дополнение к "зажатию" тайм-аут может также срабатывать позже, когда страница (или сама ОС / браузер) занята другими задачами.

Другими словами, способ, которым setTimeout обычно реализуется, он просто предназначен для выполнения после заданной задержки, и как только поток браузера может выполнить его.

Однако разные браузеры могут реализовывать это по-разному. Вот несколько тестов, которые я сделал:

var date = new Date();
setTimeout(function(e) {
    var currentDate = new Date();
    console.log(currentDate-date);
}, 1000);

// Browser Test1 Test2 Test3 Test4
// Chrome    998  1014   998   998
// Firefox  1000  1001  1047  1000
// IE 11    1006  1013  1007  1005

Возможно, < 1000 раз из Chrome можно было бы объяснить неточность в Date типа, или, возможно, Chrome использует другую стратегию для принятия решения о том, когда выполнять код - возможно, он пытается вставить его в ближайший временной интервал, даже если задержка еще не завершена.

Короче говоря, вы не должны использовать setTimeout если вы ожидаете надежного, согласованного измерения времени в миллисекундах.

В общем, компьютерные программы крайне ненадежны, когда пытаются выполнять вещи с точностью более 50 мс. Причина этого заключается в том, что даже на восьмипроцессорном гиперпоточном процессоре ОС обычно манипулирует несколькими сотнями процессов и потоков, иногда тысячами и более. ОС выполняет всю эту многозадачность, распределяя их все по частям процессорного времени один за другим, а это означает, что у них "максимум несколько миллисекунд времени, чтобы сделать свое дело".

Простота означает, что если вы установите тайм-аут на 1000 мс, вероятность того, что текущий процесс браузера даже не будет запущен в этот момент времени, весьма мала, поэтому браузер не замечает, пока не будет 1005, 1010 или даже 1050 миллисекунд, что он должен выполнить данный обратный вызов.

Обычно это не проблема, это случается, и это редко имеет первостепенное значение. Если это так, все операционные системы предоставляют таймеры уровня ядра, которые намного более точны, чем 1 мс, и позволяют разработчику выполнять код точно в нужный момент времени. Однако JavaScript, как среда с сильной песочницей, не имеет доступа к подобным объектам ядра, и браузеры воздерживаются от их использования, поскольку теоретически он может позволить кому-то атаковать стабильность ОС изнутри веб-страницы, аккуратно создавая код, который лишает других нити, наводнив его множеством опасных таймеров.

Что касается того, почему тест дает 980, я не уверен - это будет зависеть от того, какой именно браузер вы используете и какой движок JavaScript. Однако я могу полностью понять, если браузер просто вручную немного корректирует загрузку и / или скорость системы, гарантируя, что "в среднем задержка будет примерно соответствовать правильному времени" - это будет иметь большой смысл от принципа "песочницы" до приблизительное количество времени, необходимое, не обременяя остальную часть системы.

Кто-то, пожалуйста, поправьте меня, если я неправильно истолковываю эту информацию:

Согласно сообщению от Джона Резига о неточности тестов производительности на разных платформах (выделено мое)

Поскольку системное время постоянно округляется до последнего запрашиваемого времени (каждое с интервалом около 15 мс), качество результатов производительности серьезно ухудшается.

Таким образом, по сравнению с системным временем на каждом конце имеется помадка до 15 мс.

У меня был похожий опыт.
Я использовал что-то вроде этого:

var iMillSecondsTillNextWholeSecond = (1000 - (new Date().getTime() % 1000));
setTimeout(function ()
{
    CountDownClock(ElementID, RelativeTime);
}, iMillSecondsTillNextWholeSecond);//Wait until the next whole second to start.

Я заметил, что это будет пропускать секунду каждые пару секунд, иногда это будет продолжаться дольше.
Тем не менее, я все равно поймал бы это Пропуск через 10 или 20 секунд, и это просто выглядело шатким
Я подумал: "Может быть, Тайм-аут слишком медленный или ждет чего-то другого?".
Затем я понял: "Может быть, это слишком быстро, и таймеры, которыми управляет браузер, отключаются на несколько миллисекунд?"

После добавления +1 миллисекунды к моей переменной я видел, как она пропускалась только один раз.
Я закончил тем, что добавил +50 мс, просто чтобы быть в безопасности.

var iMillSecondsTillNextWholeSecond = (1000 - (new Date().getTime() % 1000) + 50);

Я знаю, это немного смешно, но мой таймер работает без сбоев.:)

В Javascript есть способ работать с точными временными рамками. Вот один подход:

Вы могли бы просто сохранитьDate.nowкогда начинаешь ждать, и создаешь интервал с низким кадром обновления мс, и вычисляешь разницу между датами.

Пример:

      const startDate = Date.now()

setInterval(() => {
  const currentDate = Date.now()

  if (currentDate - startDate === 1000 {
    // it was a second

    clearInterval()
    return
  } 

  // it was not a second
}, 50)
Другие вопросы по тегам