> Как работают 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 - бесконечный цикл:
- Проверяет call stack. Если он не пуст - ждёт.
- Когда стек пуст, полностью опустошает microtask queue (включая микротаски, добавленные во время выполнения других микротасок).
- Берёт одну макротаску из callback queue и выполняет её.
- Повторяет.
Ключевой момент: микротаски выполняются до следующей макротаски, но после текущего синхронного кода. Это значит, что промисы могут "голодать" макротаски, если постоянно добавляют новые микротаски.
На практике
Для фронтенд-разработчика это важно при:
- обработке пользовательских событий - они макротаски, поэтому могут быть задержаны микротасками;
- оптимизации рендеринга - браузер может отрисовать кадр только когда стек и микротаски пусты;
- работе с анимациями и requestAnimationFrame - это отдельный механизм, не входящий в callback queue;
- отладке асинхронного кода - понимание порядка помогает предсказать, что выполнится раньше.
Практическое правило: если нужно выполнить код после текущей синхронной работы, но до следующей макротаски - используй Promise.resolve().then() или queueMicrotask. Если нужна задержка или отложенная задача - setTimeout.
Пример кода
JAVASCRIPTconsole.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
Более сложный случай с вложенными микротасками:
JAVASCRIPTsetTimeout(() => 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.
> Похожие задачи по frontend
Как обращаться к объекту массива через this в JavaScript
Сколько времени занял рефакторинг и каков был объем работы
В чем разница между стрелочной и обычной функцией в контексте this в JavaScript?
Какие события жизненного цикла HTML-страницы существуют
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью