Почему sleep(100) отработает примерно через 100 мс и может ли быть 99 мс?
Что проверяют
Понимаете ли вы, что таймер в браузере это обещание «не раньше», а не «ровно через». Вторая половина вопроса про 99 мс проверяет, отличаете ли вы неточность планировщика от неточности измерения, а это уже уровень выше базового.
Как отвечать
Блокирующего сна в JavaScript нет, его подменяют промисом вокруг таймера:
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));Спецификация обязывает подождать не меньше заданного времени, а затем поставить колбэк в очередь макрозадач. Выполнится он ещё позже: когда опустеет стек и разгребётся очередь микрозадач. Отсюда типичный результат чуть больше ста:
- главный поток занят длинной синхронной задачей или версткой;
- гранулярность системного таймера и планировщик ОС;
- вложенные таймеры после пятого уровня зажимаются минимумом около четырёх миллисекунд;
- в неактивной вкладке интервалы режутся до секунды и реже.
Может ли выйти 99? По спецификации сработать раньше срока таймер не должен, но замер способен показать 99, и почти всегда это артефакт измерения: Date.now() это стенные часы, они прыгают при синхронизации времени и имеют грубый шаг, а performance.now() намеренно огрублён и зашумлён из-за защиты от атак по времени. Плюс округление при выводе.
Где спотыкаются
Говорят «поток засыпает». Движок в это время полностью свободен, он крутит другие задачи. А вот попытка сделать настоящий сон циклом while по часам не приостанавливает вкладку, а замораживает её вместе с реакцией на клики.
Строят на таймере метроном или анимацию и удивляются накопленной ошибке: каждый следующий запуск прибавляет свою задержку к предыдущей. Для кадров есть requestAnimationFrame, для звука часы аудиоконтекста, а интервал всегда пересчитывают от опорной метки, а не прибавляют шаг.
Смешивают задержку и порядок очередей. setTimeout с нулём выполнится позже уже готового промиса, потому что промисы это микрозадачи, и цикл разгребает их целиком перед следующей макрозадачей.