Что такое Event Loop и в каком порядке выполняются microtasks и macrotasks в современных версиях V8?
Event Loop: общая модель
Event Loop — это механизм, который позволяет JavaScript оставаться однопоточным, но не блокировать среду выполнения (браузер, Node.js). Он постоянно проверяет call stack: как только стек пустеет, движок берёт следующую задачу из очередей и выполняет её.
Есть две категории очередей.
Macrotasks (задачи)
- setTimeout, setInterval
- I/O операции, сетевые события (Node.js)
- UI-события — клики, скролл (браузер)
- postMessage, MessageChannel
Microtasks (микрозадачи)
- Promise.then/catch/finally
- queueMicrotask
- MutationObserver (браузер)
- process.nextTick (Node.js — отдельная очередь с более высоким приоритетом, чем у обычных microtasks)
Порядок выполнения
Один цикл event loop:
- Достать и выполнить ровно одну macrotask из очереди.
- Полностью опустошить очередь microtasks — включая те, что добавились во время выполнения других microtasks.
- При необходимости выполнить рендеринг (браузер).
- Перейти к следующей macrotask.
Ключевой момент: microtasks выполняются между каждой macrotask, а не после всех сразу. Если во время выполнения microtask добавляется новая microtask, она тоже отработает до перехода к следующей macrotask — очередь дренируется полностью на каждом "чекпоинте".
Изменения в новых версиях V8
До V8 7.2 (Chrome 71, конец 2018) async/await реализовывался через generator и обёртки над Promise и тратил на каждый await три лишних microtask-тика вместо одного. Из-за этого код на async/await и эквивалентный код на .then() могли выполняться в разном порядке — async/await "отставал".
В V8 7.2 приняли предложение TC39 "optimize await": теперь await эквивалентен одному Promise.resolve().then(), а не трём. Порядок выполнения async/await стал идентичен цепочке .then(), лишние тики исчезли, выполнение немного ускорилось. Это важно знать: старые статьи и примеры про "разницу между async/await и Promise.then" могут быть неактуальны для современных движков (Node 12+, актуальные браузеры).
process.nextTick в Node.js
В Node.js есть отдельная очередь process.nextTick, которая обрабатывается раньше остальных microtasks (включая Promise) после каждой фазы event loop — это специфика платформы Node, а не спецификации ECMAScript.
Итог порядка приоритетов
synchronous код > process.nextTick (Node.js) > microtasks (Promise, queueMicrotask) > macrotasks (setTimeout, I/O) > рендеринг.
Что хочет услышать интервьюер
Кандидат объясняет, что microtasks выполняются полностью между каждой macrotask, а не одной пачкой в конце
Знание конкретных примеров: setTimeout — macrotask, Promise.then — microtask
Понимание, что await эквивалентен .then() по количеству тиков в современных версиях V8 (после V8 7.2), а не порождает лишние задержки как раньше
Умение предсказать порядок вывода в коде с смешанными setTimeout, Promise и async/await
Знание про process.nextTick в Node.js и его приоритет выше обычных microtasks
Пример: Порядок выполнения microtasks и macrotasks
console.log('1: синхронный код');
setTimeout(() => {
console.log('4: macrotask (setTimeout)');
}, 0);
Promise.resolve().then(() => {
console.log('3: microtask (Promise.then)');
});
queueMicrotask(() => {
console.log('3.5: microtask (queueMicrotask)');
});
console.log('2: снова синхронный код');
// Вывод:
// 1: синхронный код
// 2: снова синхронный код
// 3: microtask (Promise.then)
// 3.5: microtask (queueMicrotask)
// 4: macrotask (setTimeout)
Пример: async/await эквивалентен .then() начиная с V8 7.2
async function example() {
console.log('a');
await null; // эквивалентно одному Promise.resolve().then()
console.log('b');
}
example();
Promise.resolve().then(() => console.log('c'));
// Вывод в современных движках (V8 7.2+):
// a
// c
// b
// До V8 7.2 'b' могло вывестись после лишних дополнительных тиков
Типичные ошибки
Утверждение, что все microtasks выполняются только после всех macrotasks в очереди, а не между каждой из них
Путаница между process.nextTick и обычными Promise-микрозадачами в Node.js
Ссылка на устаревшее поведение V8 (до версии 7.2), где async/await добавлял лишние тики по сравнению с .then()
Неверный порядок предсказания вывода в задачах с вложенными setTimeout и Promise.then
Смешивание понятий 'очередь задач' и 'call stack', будто это одно и то же


