Вопрос проверяет понимание приоритетов выполнения асинхронных задач в Event Loop и причин “неожиданного” порядка логов.
Microtask выполняются раньше macrotask. Сначала выполняется весь синхронный код, затем очищается очередь microtask, и только потом берётся следующая macrotask. Promise.then и queueMicrotask относятся к microtask, а setTimeout — к macrotask. Это влияет на порядок выполнения кода и рендеринг. Понимание порядка помогает правильно писать асинхронную логику.
Event Loop выполняет задачи не просто “по очереди”, а с приоритетом: microtask имеют более высокий приоритет, чем macrotask.
Microtask queue
Promise.then / catch / finally
queueMicrotask
MutationObserver
Macrotask queue (task queue)
setTimeout / setInterval
события UI (например, click)
сетевые события (в упрощённом понимании)
postMessage (часто рассматривают как task)
В одном “цикле” Event Loop типичный порядок такой:
Выполняется весь синхронный код (пока call stack не станет пустым)
Затем выполняются все microtask, которые накопились
Затем браузер может выполнить рендер (если нужно)
Потом берётся следующая macrotask и цикл повторяется
Важно: microtask выполняются до полного опустошения очереди. Если внутри microtask добавить ещё microtask, они тоже будут выполнены в этом же проходе, прежде чем Event Loop перейдёт к macrotask.
console.log('start')
setTimeout(() => console.log('timeout'), 0)
Promise.resolve().then(() => console.log('promise'))
console.log('end')
Ожидаемый порядок:
start
end
promise
timeout
Если “перегрузить” microtask (например, цепочками Promise.then), можно:
откладывать обработку setTimeout
задерживать реакцию UI
ухудшить отзывчивость приложения
Вывод:
В JavaScript после синхронного кода всегда сначала выполняются microtask, и только потом macrotask — это ключ к пониманию порядка выполнения асинхронности.