Подтвердить что ты не робот

Почему 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:

Enter image description here

4b9b3361

Ответ 1

Он не должен быть особенно точным. Существует ряд факторов, ограничивающих, как скоро браузер сможет выполнить код; цитируя 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, если вы ожидаете надежного, последовательного, миллисекундного времени.

Ответ 2

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

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

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

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

Ответ 3

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

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

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

Таким образом, по сравнению с системным временем существует до 15 фунтов стерлингов на обоих концах.

Ответ 4

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

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

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

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

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

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