Встречается на 23% собеседований по JavaScript

Как работает EventLoop

EventLoop — это механизм в JavaScript, который управляет выполнением кода, обработкой событий и колбэков. Он работает в однопоточном режиме, но использует очередь задач для асинхронных операций, что позволяет не блокировать основной поток. Понимание EventLoop критично для написания эффективного асинхронного кода и избежания ошибок, связанных с порядком выполнения.

Как это работает

EventLoop основан на нескольких ключевых компонентах:

  • Call Stack — стек вызовов, где выполняются синхронные функции. Когда функция вызывается, она добавляется в стек, а когда завершается — удаляется.
  • Task Queue (очередь макрозадач) — сюда попадают колбэки от setTimeout, setInterval, событий DOM и других макрозадач.
  • Microtask Queue (очередь микрозадач) — сюда попадают колбэки от промисов (Promise.then, catch, finally), queueMicrotask, а также MutationObserver.

Алгоритм работы:

  1. Выполняется весь синхронный код, пока Call Stack не опустеет.
  2. Затем EventLoop обрабатывает все микрозадачи из Microtask Queue, пока она не станет пустой.
  3. После этого берется одна макрозадача из Task Queue и выполняется.
  4. Цикл повторяется: снова обрабатываются микрозадачи, затем следующая макрозадача, и так далее.

Важно, что микрозадачи имеют приоритет над макрозадачами. Это означает, что после каждого макрозадачи (или синхронного блока) все накопившиеся микрозадачи будут выполнены до перехода к следующей макрозадаче.

Пример

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

javascript
console.log('Start');

setTimeout(() => console.log('Timeout'), 0);

Promise.resolve().then(() => console.log('Promise'));

console.log('End');

Вывод в консоль:

text
Start
End
Promise
Timeout

Почему так происходит:

  • Сначала выполняется синхронный код: console.log('Start') и console.log('End').
  • setTimeout с задержкой 0 регистрирует макрозадачу, но она попадает в Task Queue.
  • Promise.resolve().then(...) создает микрозадачу, которая попадает в Microtask Queue.
  • После завершения синхронного кода EventLoop обрабатывает микрозадачи: выводит Promise.
  • Затем берет первую макрозадачу из Task Queue: выводит Timeout.

Подводные камни

  • Задержка setTimeout не гарантирует точное время выполнения. Даже с 0 мс колбэк выполнится только после того, как Call Stack освободится и будут обработаны все микрозадачи. Это может привести к неожиданным задержкам.
  • Бесконечные микрозадачи могут заблокировать EventLoop. Если микрозадача создает новую микрозадачу (например, рекурсивный Promise.resolve().then(...)), то макрозадачи никогда не будут выполнены, и страница зависнет.
  • События DOM также являются макрозадачами. Обработчики кликов, скролла и других событий попадают в Task Queue и выполняются после микрозадач.
  • async/await — это синтаксический сахар над промисами. await заставляет функцию приостановиться и продолжиться как микрозадача, поэтому порядок выполнения аналогичен промисам.

Когда использовать

Понимание EventLoop помогает в следующих ситуациях:

  • Оптимизация производительности: избегайте тяжелых синхронных операций, которые блокируют Call Stack и задерживают обработку событий.
  • Управление порядком асинхронных операций: используйте микрозадачи для операций, которые должны выполниться как можно скорее после текущего синхронного кода, и макрозадачи для отложенных действий.
  • Отладка асинхронного кода: знание порядка выполнения помогает предсказать, когда сработают колбэки, и избежать гонок.

Коротко

  • EventLoop — однопоточный цикл, который управляет выполнением кода через Call Stack, Microtask Queue и Task Queue.
  • Сначала выполняется синхронный код, затем все микрозадачи, потом одна макрозадача, и цикл повторяется.
  • Микрозадачи (промисы) имеют приоритет над макрозадачами (setTimeout, события).
  • Избегайте бесконечных микрозадач и помните, что setTimeout(0) не гарантирует мгновенное выполнение.
как отвечать на вопрос
пример собеседования
фреймворки на собеседовании
типичные вопросы junior
интервью вопросы и ответы