Расскажи про event loop

Event loop — это механизм, обеспечивающий неблокирующее (асинхронное) выполнение кода в однопоточном окружении, таком как браузерный JavaScript или Node.js. Он позволяет выполнять несколько задач «одновременно», даже если язык или среда работают в одном потоке, за счёт постановки задач в очередь и их последовательной обработки.

🧠 Общая идея

Основной поток выполнения (main thread) может делать только одну вещь за раз. Чтобы не блокировать его долгими операциями (например, загрузкой данных с сервера, таймерами, обработкой событий), используется event loop, который:

  1. Получает задания (callback-и, promise-и, таймеры, I/O),

  2. Кладёт их в очереди,

  3. Последовательно вызывает их, когда основной стек вызовов освобождается.

⚙️ Компоненты 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 (цикл событий)

Бесконечный цикл, который:

  1. Проверяет, пуст ли стек вызовов.

  2. Если да — сначала исполняет все микрозадачи (микротики).

  3. Затем берёт одну задачу из callback-очереди и кладёт её в стек.

  4. Повторяет.

🔄 Как работает цикл событий (в пошаговом виде)

  1. Выполняется основной код (синхронно), попадает в call stack.

  2. Асинхронные вызовы (setTimeout, fetch, Promises) передаются в Web API/Node API.

  3. Когда они завершаются:

    • колбэки setTimeout, fetch, событий DOM → в очередь задач (callback queue),

    • колбэки Promise.then → в очередь микрозадач (microtask queue).

  4. Event loop проверяет call stack: если пуст — берёт микрозадачи и выполняет все.

  5. Потом берёт одну задачу из callback queue и кладёт в стек.

  6. Повторяет.

📦 Пример: setTimeout и Promise

console.log('start');
setTimeout(() => {
console.log('timeout');
}, 0);
Promise.resolve().then(() => {
console.log('promise');
});
console.log('end');

Порядок вывода:

start

end

promise

timeout

Объяснение:

  1. console.log('start') и console.log('end') — синхронные, попадают в call stack и сразу выполняются.

  2. setTimeout(..., 0) → Web API → callback идёт в callback queue.

  3. Promise.resolve().then(...) → микрозадача → в microtask queue.

  4. После end, call stack пуст.

  5. Event loop сначала выполняет все микрозадачи → promise.

  6. Затем берёт из 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

Почему?

  1. Первый setTimeout → стек → печать A → микрозадача B → в очередь микрозадач.

  2. Прежде чем перейти к следующему setTimeout, event loop выполняет все микрозадачи → B.

  3. Затем только 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 — один из центральных механизмов в любом современном асинхронном окружении. Он позволяет эффективно обрабатывать множество задач, не блокируя главный поток исполнения.