Event loop
Вопрос
Чем microtask отличается от macrotask, и в каком порядке выполнится код с setTimeout, Promise.then и обычным вызовом?
Короткий ответ
Синхронный код выполняется сразу, в текущем стеке. Когда стек пустеет, event loop сначала опустошает очередь микрозадач, и только потом берёт одну макрозадачу. Promise.then, queueMicrotask и MutationObserver — микрозадачи. setTimeout, setInterval, события ввода и большинство колбэков I/O — макрозадачи.
console.log('1')
setTimeout(() => console.log('2'), 0)
Promise.resolve().then(() => console.log('3'))
console.log('4')
// 1, 4, 3, 2Разбор
Движок не «ждёт таймер в фоне внутри JS». Хост (браузер или Node) кладёт колбэк в очередь, когда таймер сработал. Даже при задержке 0 колбэк setTimeout не влезет перед микрозадачами, которые уже запланированы.
Микрозадачи, поставленные из микрозадачи, тоже выполнятся до следующей макрозадачи. Бесконечный then может надолго отложить отрисовку и обработку клика.
async-функция до первого await идёт синхронно. После await продолжение попадает в микрозадачи, потому что await разворачивается через промис.
На что смотрят на senior
Связка с UI: тяжёлая микрозадача блокирует кадр так же, как длинный синхронный цикл. setTimeout(fn, 0) не «отдаёт поток немедленно» в смысле микрозадач, но даёт браузеру шанс взять следующую макрозадачу, включая отрисовку, если микроочередь пуста. Для разбиения длинной работы ближе scheduler.yield, requestAnimationFrame или нарезка на чанки, а не надежда на нулевой таймер.
В Node к макрозадачам добавляются setImmediate и фазы poll, но на фронтенд-собеседовании обычно хватает браузерной модели: стек, микрозадачи, затем рендер и макрозадачи. Точный порядок «рендер между макрозадачами» стоит оговорить: браузер старается отрисовать кадр, когда очередь микрозадач пуста и нет более срочной работы.
Типичные ошибки
- Считать, что
setTimeout(0)выполнится раньше уже созданногоPromise.then. - Путать очередь микрозадач с очередью таймеров.
- Думать, что
asyncделает всю функцию асинхронной с первой строки.
Задача
В каком порядке появятся строки?
console.log('A')
setTimeout(() => console.log('B'), 0)
Promise.resolve().then(() => console.log('C'))
console.log('D')Показать ответ
Синхронный код идёт сразу: A, затем D. then — микрозадача, она выполняется раньше таймера. B — макрозадача, она последняя.
Порядок: A, D, C, B.