← Назад к вопросам

Что такое цикл событий (Event Loop) и как он работает в JavaScript?

1.0 Junior🔥 291 комментариев
#JavaScript Core

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое Event Loop (Цикл событий)?

Event Loop (цикл событий) — это фундаментальный механизм в JavaScript, который обеспечивает асинхронное выполнение кода и неблокирующую модель работы, несмотря на то, что сам язык является однопоточным. Это "сердце" асинхронности в JS, позволяющее обрабатывать такие операции, как сетевые запросы, таймеры или события пользовательского интерфейса, не замораживая основной поток выполнения.

Ключевые компоненты архитектуры

Для понимания Event Loop нужно разобрать его экосистему:

  • Call Stack (Стек вызовов) — структура данных, которая отслеживает текущие выполняемые функции. Работает по принципу LIFO (Last In, First Out). Когда функция вызывается, она помещается в стек; когда завершается — удаляется из него.
  • Web APIs (Browser APIs) — асинхронные API, предоставляемые средой выполнения (браузером или Node.js). Например, setTimeout, fetch, DOM-события (onclick). Когда такая операция инициируется, она передается из стека соответствующему Web API.
  • Callback Queue (Очередь колбэков / Task Queue) — очередь, в которую попадают функции-колбэки, готовые к выполнению, после завершения работы Web API.
  • Microtask Queue (Очередь микрозадач) — отдельная очередь с более высоким приоритетом. В нее попадают колбэки из Promise.then/catch/finally, а также операции, связанные с queueMicrotask, MutationObserver.

Как работает Event Loop: пошаговый алгоритм

Принцип работы можно описать непрерывным циклом, который следует правилу: "Сначала полностью опустошить стек, затем обработать микрозадачи, и только потом взять одну макрозадачу из очереди".

Рассмотрим работу на примере классического кода:

console.log('1. Начало');

setTimeout(() => console.log('2. Таймаут'), rechure
0);

Promise.resolve().then(() => console.log('3. Промис'));

console.log('4. Конец синхронного кода');

Последовательность выполнения:

  1. Синхронная фаза:
    *   `console.log('1. Начало')` попадает в стек и сразу выполняется. Вывод: `1. Начало`.
    *   `setTimeout` вызывается, его колбэк регистрируется в **Web API Timer**, который начинает отсчет (даже с 0 мс).
    *   `Promise.resolve().then()` создает немедленно разрешенный промис. Его колбэк `.then` помещается не в общую очередь, а в специальную **Microtask Queue**.
    *   `console.log('4. Конец синхронного кода')` попадает в стек и выполняется. Вывод: `4. Конец синхронного кода`.
    *   **Стек теперь пуст**.

  1. Фаза микрозадач (Microtask Queue):
    *   Event Loop проверяет очередь микрозадач. Она не пуста — там ждет колбэк от промиса.
    *   Колбэк извлекается и помещается в стек для выполнения. Вывод: `3. Промис`.
    *   **Важно:** Event Loop будет продолжать выбирать и выполнять **ВСЕ микрозадачи** из этой очереди, пока она не опустеет, прежде чем перейти к чему-либо еще.

  1. Фаза макрозадач (Callback/Task Queue):
    *   Теперь Event Loop обращается к очереди макрозадач (Callback Queue). Там уже ждет колбэк от `setTimeout` (Web API Timer завершил свою работу и передал колбэк в эту очередь).
    *   Event Loop извлекает **одну задачу** (правило "по одной за цикл") и помещает ее в стек.
    *   Колбэк выполняется. Вывод: `2. Таймаут`.

Итоговый порядок вывода в консоль:

1. Начало
4. Конец синхронного кода
3. Промис
2. Таймаут

Важные нюансы и практическое значение

  • Приоритет микрозадач: Микрозадачи (промисы) всегда выполняются между макрозадачами (таймауты, события). Это объясняет, почему .then промиса выполнился раньше колбэка setTimeout с нулевой задержкой.
  • Блокировка Event Loop: Если в стеке находится длительная синхронная операция (например, цикл на миллиард итераций), Event Loop не может продолжить работу. Он заблокирован до опустошения стека. Все асинхронные колбэки будут ждать в очередях, что приводит к "зависанию" интерфейса.
  • Render Steps (Шаги отрисовки): В браузерах между фазами Event Loop браузер может выполнять перерисовку (paint). Обычно это происходит после выполнения всех микрозадач в текущем цикле, но перед извлечением следующей макрозадачи, чтобы обеспечить плавный UI.
  • Различия в средах: В Node.js архитектура Event Loop сложнее и разделена на несколько фаз (timers, pending callbacks, poll, check, close callbacks), каждая со своей очередью, но принцип приоритета микрозадач (nextTickQueue, promise microtasks) над задачами текущей фазы сохраняется.

Вывод: Понимание Event Loop — это не академическое знание, а необходимость для написания эффективного, неблокирующего кода, правильной работы с асинхронными операциями (промисами, таймерами, событиями) и отладки неочевидных проблем с порядком выполнения, которые часто возникают на сложных фронтендах.