Как работает EventLoop
EventLoop — это механизм в JavaScript, который управляет выполнением кода, обработкой событий и колбэков. Он работает в однопоточном режиме, но использует очередь задач для асинхронных операций, что позволяет не блокировать основной поток. Понимание EventLoop критично для написания эффективного асинхронного кода и избежания ошибок, связанных с порядком выполнения.
Как это работает
EventLoop основан на нескольких ключевых компонентах:
- Call Stack — стек вызовов, где выполняются синхронные функции. Когда функция вызывается, она добавляется в стек, а когда завершается — удаляется.
- Task Queue (очередь макрозадач) — сюда попадают колбэки от
setTimeout,setInterval, событий DOM и других макрозадач. - Microtask Queue (очередь микрозадач) — сюда попадают колбэки от промисов (
Promise.then,catch,finally),queueMicrotask, а такжеMutationObserver.
Алгоритм работы:
- Выполняется весь синхронный код, пока Call Stack не опустеет.
- Затем EventLoop обрабатывает все микрозадачи из Microtask Queue, пока она не станет пустой.
- После этого берется одна макрозадача из Task Queue и выполняется.
- Цикл повторяется: снова обрабатываются микрозадачи, затем следующая макрозадача, и так далее.
Важно, что микрозадачи имеют приоритет над макрозадачами. Это означает, что после каждого макрозадачи (или синхронного блока) все накопившиеся микрозадачи будут выполнены до перехода к следующей макрозадаче.
Пример
Рассмотрим классический пример, демонстрирующий порядок выполнения:
console.log('Start');
setTimeout(() => console.log('Timeout'), 0);
Promise.resolve().then(() => console.log('Promise'));
console.log('End');Вывод в консоль:
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)не гарантирует мгновенное выполнение.
