Расскажи про event loop
Event loop — это механизм, обеспечивающий неблокирующее (асинхронное) выполнение кода в однопоточном окружении, таком как браузерный JavaScript или Node.js. Он позволяет выполнять несколько задач «одновременно», даже если язык или среда работают в одном потоке, за счёт постановки задач в очередь и их последовательной обработки.
🧠 Общая идея
Основной поток выполнения (main thread) может делать только одну вещь за раз. Чтобы не блокировать его долгими операциями (например, загрузкой данных с сервера, таймерами, обработкой событий), используется event loop, который:
-
Получает задания (callback-и, promise-и, таймеры, I/O),
-
Кладёт их в очереди,
-
Последовательно вызывает их, когда основной стек вызовов освобождается.
⚙️ Компоненты event loop
1. Call Stack (Стек вызовов)
Стек, куда помещаются функции, которые сейчас выполняются. Работает по принципу LIFO (последний пришёл — первый вышел). Если стек не пуст, никакие другие задачи не могут начаться.
2. Web APIs / Node APIs
В браузере это асинхронные API, предоставляемые окружением: setTimeout, fetch, addEventListener, DOM события и т.д. Они не выполняются в JavaScript движке (например, V8), а отдаются на исполнение во внешние API.
В Node.js аналогично — асинхронные операции, как файловая система, сеть и др.
3. Callback Queue (очередь обратных вызовов)
Когда асинхронная операция завершается, её callback попадает в эту очередь, ожидая, когда event loop передаст её в стек.
4. Microtask Queue (очередь микрозадач)
Сюда попадают задачи, которые создаются через:
-
Promise.then/catch/finally
-
MutationObserver
-
queueMicrotask
Эта очередь имеет более высокий приоритет, чем обычная очередь callback-ов.
5. Event Loop (цикл событий)
Бесконечный цикл, который:
-
Проверяет, пуст ли стек вызовов.
-
Если да — сначала исполняет все микрозадачи (микротики).
-
Затем берёт одну задачу из callback-очереди и кладёт её в стек.
-
Повторяет.
🔄 Как работает цикл событий (в пошаговом виде)
-
Выполняется основной код (синхронно), попадает в call stack.
-
Асинхронные вызовы (setTimeout, fetch, Promises) передаются в Web API/Node API.
-
Когда они завершаются:
-
колбэки setTimeout, fetch, событий DOM → в очередь задач (callback queue),
-
колбэки Promise.then → в очередь микрозадач (microtask queue).
-
-
Event loop проверяет call stack: если пуст — берёт микрозадачи и выполняет все.
-
Потом берёт одну задачу из callback queue и кладёт в стек.
-
Повторяет.
📦 Пример: setTimeout и Promise
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') — синхронные, попадают в call stack и сразу выполняются.
-
setTimeout(..., 0) → Web API → callback идёт в callback queue.
-
Promise.resolve().then(...) → микрозадача → в microtask queue.
-
После end, call stack пуст.
-
Event loop сначала выполняет все микрозадачи → promise.
-
Затем берёт из callback queue → timeout.
📘 Очереди в приоритете
Всегда выполняются сначала микрозадачи, потом макрозадачи (callback-и таймеров, I/O и др.).
setTimeout(() => {
console.log('timeout');
}, 0);
queueMicrotask(() => {
console.log('microtask');
});
Результат:
microtask
timeout
🧪 Пример с вложенным setTimeout и Promise
setTimeout(() => {
console.log('A');
Promise.resolve().then(() => {
console.log('B');
});
}, 0);
setTimeout(() => {
console.log('C');
}, 0);
Результат:
A
B
C
Почему?
-
Первый setTimeout → стек → печать A → микрозадача B → в очередь микрозадач.
-
Прежде чем перейти к следующему setTimeout, event loop выполняет все микрозадачи → B.
-
Затем только C.
🧠 Event loop в Node.js
Отличия от браузера:
-
Node.js использует libuv — C++ библиотеку для асинхронности.
-
Цикл событий более сложный и делится на фазы:
Фаза | Что происходит |
---|---|
timers | setTimeout, setInterval |
--- | --- |
pending callbacks | специфичные I/O callbacks |
--- | --- |
idle, prepare | внутренние |
--- | --- |
poll | ждёт событий I/O |
--- | --- |
check | setImmediate() |
--- | --- |
close callbacks | закрытие сокетов и т.д. |
--- | --- |
В каждой фазе также исполняются микрозадачи после фазы, до перехода к следующей.
🧩 Зачем нужен event loop?
- **Асинхронность в однопоточном мире.
** -
UI не блокируется при долгих операциях (загрузка, ожидание сети).
-
**Обработка событий и пользовательского ввода.
** - **Основной механизм реализации Promises, async/await.
**
📌 Где ещё встречается event loop?
-
JavaScript в браузерах (V8 в Chrome, SpiderMonkey в Firefox)
-
Node.js
-
Python (через asyncio)
-
Dart (в Flutter)
-
Rust (через async-исполнители, например tokio)
Event loop — один из центральных механизмов в любом современном асинхронном окружении. Он позволяет эффективно обрабатывать множество задач, не блокируя главный поток исполнения.