JavaScript

Как работает Event Loop?

Event Loop (Цикл событий)

JavaScript - однопоточный язык. Event Loop - механизм, который позволяет выполнять асинхронный код, не блокируя основной поток.

Схема

console.log("1") sync
setTimeout(cb, 0) macro
Promise.resolve() micro
.then(cb)
console.log("4") sync
console output
1
4
3 (microtask)
2 (macrotask)
CALL STACK
console.log("1")
WEB API
setTimeout
timers, fetch,
DOM events...
EL перемещает callback
→ в очередь
EVENT
LOOP
micro

macro
MICROTASK QUEUE
Promise cb
← первым!
MACROTASK QUEUE
setTimeout cb
← потом

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

Event Loop - это бесконечный цикл, который следит за Call Stack. Если стек пуст, он берёт следующую задачу из очереди и отправляет её на выполнение.

Любая функция сначала попадает в Call Stack и выполняется там. Если функция асинхронная (например, setTimeout или fetch) - она уходит в Web APIs, которые работают в фоне. Когда асинхронная операция завершается, её колбэк попадает в очередь: микрозадачи (Promise.then, queueMicrotask) или макрозадачи (setTimeout, setInterval).

Event Loop проверяет: как только Call Stack пуст - сначала полностью опустошает очередь микрозадач, затем берёт одну макрозадачу, снова микрозадачи - и так по кругу.

Порядок выполнения

  1. Сначала выполняется весь синхронный код - последовательно, строка за строкой.
  2. Затем выполняются все микрозадачи (Promise.then, queueMicrotask, MutationObserver) до полного опустошения очереди. Если во время выполнения появляется новая микрозадача, она также будет выполнена сразу.
  3. После этого из очереди берётся одна макрозадача (setTimeout, setInterval, события и т.д.) и выполняется.
  4. По завершении макрозадачи снова обрабатываются все накопившиеся микрозадачи.
  5. Затем цикл повторяется: одна макрозадача → все микрозадачи → следующая макрозадача.
console.log("1")

setTimeout(() => console.log("2"), 0)   // макрозадача

Promise.resolve().then(() => console.log("3"))  // микрозадача

console.log("4")

// Вывод: 1, 4, 3, 2

Почему 3 раньше 2? - После выполнения синхронного кода (1, 4) event loop сначала очищает очередь микрозадач (Promise → 3), и только потом берёт макрозадачу (setTimeout → 2).

Блокировка потока

Длинный синхронный код блокирует всё: UI, события, таймеры.

// ❌ Заморозит браузер на ~5 секунд
const start = Date.now()
while (Date.now() - start < 5000) {}

// ✅ Разбить на части через setTimeout/requestAnimationFrame

requestAnimationFrame

Стоит между макрозадачами и рендером. Используется для анимаций - гарантирует выполнение перед следующим кадром (≈60 раз/сек).

Источники