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

Как работает Event Queue в Node.js?

2.0 Middle🔥 171 комментариев
#JavaScript Core

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

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

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

Event Queue и Event Loop в Node.js

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

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

Event Loop постоянно проверяет очередь событий и выполняет их в порядке FIFO (First In, First Out). Цикл выглядит так:

// Упрощённая модель Event Loop
while (eventQueue.waitForTask()) {
  const task = eventQueue.pop();
  execute(task);
  while (microtaskQueue.hasTasks()) {
    execute(microtaskQueue.pop());
  }
}

Фазы Event Loop

Event Loop в Node.js состоит из нескольких фаз:

┌───────────────────────────┐
│           timers          │ - setTimeout, setInterval
├───────────────────────────┤
│     pending callbacks     │ - отложенные операции ввода-вывода
├───────────────────────────┤
│       idle, prepare       │ - внутренние операции
├───────────────────────────┤
│     poll (опрос)          │ - новые события I/O
├───────────────────────────┤
│       check               │ - setImmediate
├───────────────────────────┤
│    close callbacks        │ - закрытие сокетов
└───────────────────────────┘

Практический пример фаз

console.log('Script start'); // 1. Синхронный код

setTimeout(() => {
  console.log('setTimeout'); // 5. Фаза timers
}, 0);

Promise.resolve()
  .then(() => {
    console.log('Promise'); // 3. Микротаск (microtask queue)
  });

setImmediate(() => {
  console.log('setImmediate'); // 6. Фаза check
});

console.log('Script end'); // 2. Синхронный код

// Вывод:
// Script start
// Script end
// Promise
// setTimeout
// setImmediate

Макротаски vs Микротаски

Макротаски (Macrotasks) — выполняются по одному в каждой итерации Event Loop:

  • setTimeout
  • setInterval
  • setImmediate
  • I/O операции
  • UI рендеринг (в браузере)

Микротаски (Microtasks) — выполняются полностью после каждого макротаска, перед переходом к следующему:

  • Promise (.then, .catch, .finally)
  • async/await
  • queueMicrotask()
  • MutationObserver (в браузере)
console.log('1');

setTimeout(() => {
  console.log('2'); // Макротаск
  Promise.resolve().then(() => console.log('3')); // Микротаск внутри макротаска
}, 0);

Promise.resolve()
  .then(() => {
    console.log('4'); // Микротаск
    setTimeout(() => console.log('5'), 0); // Макротаск из микротаска
  });

console.log('6');

// Вывод:
// 1
// 6
// 4
// 2
// 3
// 5

Практический пример с асинхронными операциями

const fs = require('fs');

console.log('START');

// Макротаск: I/O операция
fs.readFile('file.txt', () => {
  console.log('File read (I/O)'); // Выполнится в фазе poll
  setImmediate(() => {
    console.log('setImmediate in I/O'); // Выполнится в фазе check
  });
});

// Макротаск: таймер
setTimeout(() => {
  console.log('setTimeout');
}, 0);

// Микротаск: Promise
Promise.resolve()
  .then(() => {
    console.log('Promise'); // Выполнится сразу после синхронного кода
  });

console.log('END');

// Примерный вывод:
// START
// END
// Promise
// setTimeout
// File read (I/O)
// setImmediate in I/O

Как писать эффективный код

// Плохо — блокирует Event Loop
function slowCalculation() {
  let sum = 0;
  for (let i = 0; i < 1000000000; i++) {
    sum += i; // Синхронный код заблокирует весь Event Loop
  }
  return sum;
}

// Хорошо — используй асинхронность
function slowCalculationAsync() {
  return new Promise((resolve) => {
    setImmediate(() => {
      let sum = 0;
      for (let i = 0; i < 1000000000; i++) {
        sum += i;
      }
      resolve(sum);
    });
  });
}

// Или используй рабочие потоки для тяжёлых вычислений
const { Worker } = require('worker_threads');
const worker = new Worker('./heavy-compute.js');

Ключевые моменты

  • Event Loop — это сердце Node.js, позволяющее обрабатывать множество операций
  • Микротаски выполняются перед макротасками
  • Не блокируй Event Loop синхронным кодом
  • setImmediate выполняется раньше, чем setTimeout при setTimeout с задержкой 0
  • Используй async/await для улучшения читаемости асинхронного кода
  • Для тяжёлых вычислений используй Worker Threads или разбивай на части