> Каков порядок выполнения кода с setTimeout, Promise и цепочкой then/catch в JavaScript? (JavaScript)

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

Компании: VK, Exness

Стек: JavaScript

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

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

Сначала выполняется синхронный код, затем микрозадачи (Promise.then/catch), потом макрозадачи (setTimeout). Promise.then/catch попадают в очередь микрозадач, а setTimeout - в очередь макрозадач. Event loop сначала обрабатывает все микрозадачи, затем берёт одну макрозадачу. Поэтому Promise выполняется раньше setTimeout, даже если таймер установлен на 0 мс.

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

Порядок выполнения в JavaScript определяется event loop и очередями задач. Синхронный код выполняется сразу в текущем стеке вызовов. Асинхронные операции распределяются по двум очередям:

  • Микрозадачи: Promise.then/catch/finally, queueMicrotask, MutationObserver. После завершения каждой макрозадачи event loop проверяет и выполняет все микрозадачи до опустошения очереди.
  • Макрозадачи: setTimeout, setInterval, I/O, UI rendering. Event loop берёт одну макрозадачу за раз.

Когда встречается Promise, его executor выполняется синхронно, а then/catch регистрируются как микрозадачи. setTimeout регистрирует макрозадачу. После завершения синхронного кода event loop сначала обрабатывает все накопившиеся микрозадачи (Promise), затем переходит к макрозадачам (setTimeout). Это объясняет, почему Promise.then выполняется раньше setTimeout, даже при нулевой задержке.

На практике

Этот порядок критичен при построении цепочек асинхронных операций. Если нужно гарантировать выполнение после всех Promise, используйте await или then. Для отложенного выполнения после текущего цикла событий - setTimeout. Ошибка в понимании приводит к race conditions, когда код с setTimeout выполняется неожиданно позже Promise. В реальных проектах это влияет на порядок обновления UI, обработку данных и тайминги анимаций.

Пример кода

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'));
console.log('5: sync');
// Вывод:
// 1: sync
// 5: sync
// 3: microtask 1
// 4: microtask 2
// 2: macrotask

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

Начни с ключевого различия между микрозадачами и макрозадачами. Объясни, что event loop обрабатывает микрозадачи до макрозадач. Приведи конкретный пример с выводом консоли. Упомяни, что Promise.then - микрозадача, а setTimeout - макрозадача. Подчеркни, что это поведение стандартизировано в спецификации HTML и ECMAScript. Если спросят про async/await - объясни, что await - это синтаксический сахар над Promise, и последующий код выполняется как микрозадача.

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

Интервьюер проверяет понимание event loop, очередей задач и приоритетов выполнения асинхронного кода. Важно показать знание разницы между microtask и macrotask, а также умение предсказывать порядок вывода в сложных сценариях. Это базовый вопрос для middle-уровня, который выявляет глубину понимания асинхронности в JavaScript.

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

  • Утверждение, что setTimeout с 0 мс выполняется мгновенно или сразу после синхронного кода.
  • Путаница между очередями: думать, что Promise.then - макрозадача.
  • Игнорирование того, что цепочка then создаёт несколько микрозадач, которые выполняются последовательно до перехода к макрозадачам.
  • Непонимание, что вложенные setTimeout создают новые макрозадачи, а не микрозадачи.

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

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