> Как работают call stack, callback queue, event loop, микротаски и макротаски в JavaScript (JavaScript)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: amoCRM, Cyber Interactive, Альфа-банк, Garage Eight, Т-Банк, циан, VK, Kodix, LeverX, Spotware, Домклик, IT-One, Софт Медиа Групп, Rutube, Exness, Сфера, Сбер, TYMY, Evercode, HolyWeb

Стек: JavaScript

> Пример ответа

Короткий ответ

Call stack - синхронное исполнение функций, callback queue - очередь макрозадач, event loop - механизм, который переносит задачи из очередей в стек, когда он пуст. Микротаски (promises, queueMicrotask) выполняются сразу после текущего стека, до макрозадач. Макротаски (setTimeout, setInterval, I/O) обрабатываются по одной за цикл. Event loop сначала опустошает микротаски, затем берёт одну макротаску, и так повторяется.

Подробное объяснение

JavaScript однопоточный, поэтому порядок выполнения определяется тремя структурами:

  • Call stack - LIFO-структура, хранящая активные вызовы функций. Пока стек не пуст, ничего из очередей не выполняется.
  • Callback queue (макротаски) - FIFO-очередь для setTimeout, setInterval, I/O, UI events. Каждая макротаска выполняется как отдельный шаг event loop.
  • Microtask queue - FIFO-очередь для Promise.then/catch/finally, async/await (после await), queueMicrotask, MutationObserver. Микротаски имеют приоритет над макротасками.

Event loop - бесконечный цикл:

  1. Проверяет call stack. Если он не пуст - ждёт.
  2. Когда стек пуст, полностью опустошает microtask queue (включая микротаски, добавленные во время выполнения других микротасок).
  3. Берёт одну макротаску из callback queue и выполняет её.
  4. Повторяет.

Ключевой момент: микротаски выполняются до следующей макротаски, но после текущего синхронного кода. Это значит, что промисы могут "голодать" макротаски, если постоянно добавляют новые микротаски.

На практике

Для фронтенд-разработчика это важно при:

  • обработке пользовательских событий - они макротаски, поэтому могут быть задержаны микротасками;
  • оптимизации рендеринга - браузер может отрисовать кадр только когда стек и микротаски пусты;
  • работе с анимациями и requestAnimationFrame - это отдельный механизм, не входящий в callback queue;
  • отладке асинхронного кода - понимание порядка помогает предсказать, что выполнится раньше.

Практическое правило: если нужно выполнить код после текущей синхронной работы, но до следующей макротаски - используй Promise.resolve().then() или queueMicrotask. Если нужна задержка или отложенная задача - setTimeout.

Пример кода

JAVASCRIPT
console.log('1: sync');
setTimeout(() => console.log('2: macrotask'), 0);
Promise.resolve()
.then(() => console.log('3: microtask 1'))
.then(() => console.log('4: microtask 2'));
queueMicrotask(() => console.log('5: microtask 3'));
console.log('6: sync end');
// Вывод:
// 1: sync
// 6: sync end
// 3: microtask 1
// 4: microtask 2
// 5: microtask 3
// 2: macrotask

Более сложный случай с вложенными микротасками:

JAVASCRIPT
setTimeout(() => console.log('A: macrotask 1'), 0);
Promise.resolve()
.then(() => {
console.log('B: microtask 1');
Promise.resolve().then(() => console.log('C: nested microtask'));
});
setTimeout(() => console.log('D: macrotask 2'), 0);
// Вывод:
// B: microtask 1
// C: nested microtask
// A: macrotask 1
// D: macrotask 2

Обрати внимание: вложенная микротаска выполнится до первой макротаски, потому что event loop опустошает всю microtask queue целиком.

Как отвечать на собеседовании

Начни с краткого определения каждой структуры, затем опиши цикл работы. Обязательно упомяни приоритет микротасок над макротасками. Приведи пример с выводом в консоль и объясни каждый шаг. Если спросят про async/await - объясни, что await - это синтаксический сахар над промисами, и продолжение функции становится микротаской.

Для уровня senior добавь нюансы:

  • разницу между task queue и microtask queue в браузере и Node.js (в Node есть несколько очередей: timers, I/O, check, close);
  • поведение при бесконечном добавлении микротасок - макротаски и рендеринг блокируются;
  • отличие requestAnimationFrame от макрозадач - он выполняется перед рендером, но после микротасок;
  • влияние на производительность - тяжёлые микротаски задерживают отрисовку.

Что проверяет интервьюер

Интервьюер оценивает:

  • понимание однопоточной модели JavaScript;
  • умение точно предсказать порядок выполнения асинхронного кода;
  • знание различий между микротасками и макротасками;
  • осознание практических последствий - например, почему нельзя доверять setTimeout(0) для "немедленного" выполнения;
  • способность объяснить сложное простыми словами, без путаницы в терминах.

Для senior важно показать, что ты не просто заучил схему, а понимаешь, как это влияет на реальные сценарии: обработку событий, рендеринг, взаимодействие с браузерным API.

Типичные ошибки

  • Утверждение, что setTimeout(0) выполнится сразу - на самом деле он попадёт в конец текущей макротаски.
  • Путаница между callback queue и microtask queue - часто называют всё "очередью событий".
  • Забывают, что микротаски выполняются до следующей макротаски, но после текущего синхронного кода.
  • Не учитывают, что вложенные микротаски тоже выполняются в рамках одного цикла.
  • Смешивают event loop браузера и Node.js - в Node есть фазы и дополнительные очереди.
  • Думают, что промисы всегда асинхронны - executor промиса выполняется синхронно, асинхронны только then/catch/finally.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью