Что такое Event Loop и в каком порядке выполняются microtasks и macrotasks в современных версиях V8?

MiddleJavaScript · Frontend·Обновлено 28 августа 2026
Коротко
Event Loop — механизм, который берёт задачи из очередей и выполняет их в свободном call stack; после каждой macrotask полностью опустошается очередь microtasks, а начиная с V8 7.2 async/await по числу тиков сравнялся с цепочкой .then(), убрав лишние микрозадачи.

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:

  1. Достать и выполнить ровно одну macrotask из очереди.
  2. Полностью опустошить очередь microtasks — включая те, что добавились во время выполнения других microtasks.
  3. При необходимости выполнить рендеринг (браузер).
  4. Перейти к следующей 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', будто это одно и то же

Лучшие курсы по теме

изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Feature-Sliced Design

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.5
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее