Как колбэк из setTimeout попадает к выполнению?
Что проверяют
Понимаете ли вы, что таймер считает не движок JavaScript, а окружение, и что задержка это минимальное время ожидания, а не точный момент запуска. Следом почти всегда дают код с setTimeout(..., 0), Promise.then и console.log и просят назвать порядок вывода.
Как отвечать
Пройдите путь колбэка по шагам:
- регистрация:
setTimeoutпередаёт функцию и задержку окружению (браузеру или libuv в Node.js), возвращает идентификатор и сразу отпускает поток; - ожидание: таймер идёт вне стека вызовов. Когда время вышло, колбэк не выполняется сразу, а ставится в очередь задач (macrotask);
- event loop: берёт из очереди одну задачу только тогда, когда стек вызовов пуст. После каждой задачи он полностью разбирает очередь микрозадач (
Promise.then,queueMicrotask,MutationObserver), и только потом браузер может отрисовать кадр и взять следующую задачу; - выполнение: колбэк попадает в стек и выполняется целиком, прервать его ничто не может.
Отсюда выводы. setTimeout(fn, 0) выполнится после текущего синхронного кода и после всех уже созданных микрозадач. Если основной поток занят тяжёлым циклом на 300 мс, таймер на 100 мс сработает не раньше, чем через 300.
Добавьте детали, которые показывают глубину. По спецификации HTML после пяти уровней вложенности таймеры получают минимум 4 мс. В фоновых вкладках браузеры троттлят таймеры до раза в секунду и реже. Задержка больше 2 147 483 647 мс (около 24,8 суток) переполняется, и колбэк срабатывает почти сразу. В Node.js таймеры живут в своей фазе цикла, process.nextTick и промисы выполняются раньше них, а setImmediate в фазе check.
Где спотыкаются
Говорят, что колбэк выполнится ровно через указанное время. Это нижняя граница, фактическое время зависит от загрузки потока.
Ставят setTimeout(fn, 0) и промис в одну очередь. Промис выполнится первым, потому что микрозадачи разбираются до следующей макрозадачи.
Утверждают, что setTimeout и setImmediate в Node.js всегда идут в фиксированном порядке. Из главного модуля порядок не гарантирован, а внутри колбэка ввода-вывода setImmediate срабатывает первым.