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

Что такое EventLoop?

2.0 Middle🔥 211 комментариев
#Node.js и JavaScript

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

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

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

Event Loop в Node.js

Event Loop — это одна из самых важных концепций в Node.js. Это механизм, который позволяет JavaScript, имея single-threaded природу, обрабатывать асинхронные операции и масштабировать до миллионов одновременных соединений.

Основная концепция

Event Loop постоянно "циркулирует" в ожидании событий (callbacks) для выполнения. Это позволяет Node.js быть non-blocking и асинхронным. Вместо того чтобы ждать операцию (например, чтение файла), Node.js регистрирует callback и переходит к следующей задаче. Когда операция завершена, callback добавляется в очередь для выполнения.

Архитектура Event Loop

Event Loop состоит из нескольких очередей:

1. Call Stack — основной стек выполнения

function a() {
  console.log('A');
}

function b() {
  a();
  console.log('B');
}

b();
// Output:
// A
// B

2. Macrotask Queue (Task Queue)

  • setTimeout
  • setInterval
  • setImmediate
  • I/O операции (fs, network)
  • UI rendering (в браузере)

3. Microtask Queue

  • Promise.then/catch/finally
  • process.nextTick (специфично для Node.js)
  • MutationObserver (браузер)
  • queueMicrotask

4. Animation Frame Queue (в браузере)

  • requestAnimationFrame

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

1. Выполнить весь синхронный код (Call Stack)
2. Выполнить все microtasks (Promises, nextTick)
3. Выполнить ONE macrotask
4. Выполнить все новые microtasks
5. Повторить с шага 3

Правильное понимание порядка критично:

console.log('1. Start');

setTimeout(() => {
  console.log('2. setTimeout');
}, 0);

Promise.resolve()
  .then(() => {
    console.log('3. Promise');
  });

console.log('4. End');

// Output:
// 1. Start
// 4. End
// 3. Promise
// 2. setTimeout

// Пояснение:
// 1. Start + 4. End — синхронный код выполняется первым
// 3. Promise — microtask выполняется перед macrotask
// 2. setTimeout — macrotask выполняется последним

process.nextTick vs setImmediate

В Node.js есть важное различие:

console.log('Start');

setImmediate(() => {
  console.log('setImmediate');
});

process.nextTick(() => {
  console.log('nextTick');
});

console.log('End');

// Output:
// Start
// End
// nextTick
// setImmediate

// process.nextTick выполняется ДО setImmediate
// Оба это microtasks, но nextTick имеет выше приоритет

Практические примеры

Пример 1: Asynchronous I/O

const fs = require('fs');

console.log('Start');

fs.readFile('file.txt', 'utf-8', (err, data) => {
  console.log('File read:', data);
});

console.log('End');

// Output:
// Start
// End
// File read: [contents]

// Пояснение:
// readFile асинхронен, callback добавляется в queue
// Синхронный код выполняется первым
// Когда файл прочитан, callback выполняется

Пример 2: Promise vs setTimeout

console.log('1');

setTimeout(() => {
  console.log('2');
  Promise.resolve().then(() => {
    console.log('3');
  });
}, 0);

Promise.resolve()
  .then(() => {
    console.log('4');
  })
  .then(() => {
    console.log('5');
  });

console.log('6');

// Output:
// 1
// 6
// 4
// 5
// 2
// 3

Event Loop Phases в Node.js

Event Loop проходит через несколько фаз (phases):

timers (setTimeout, setInterval)
  ↓
pending callbacks (deferred I/O callbacks)
  ↓
idle, prepare (внутреннее использование)
  ↓
poll (retrieve new I/O events)
  ↓
check (setImmediate callbacks)
  ↓
close callbacks (close callbacks)
  ↓
↻ (back to timers)

Так почему setImmediate выполняется раньше setTimeout? Потому что он выполняется в phase "check", которая идёт раньше следующей итерации "timers" phase.

Важные последствия для production

1. Blocking Event Loop

Если синхронная операция занимает много времени, весь Event Loop блокируется:

function slowSync() {
  const start = Date.now();
  while (Date.now() - start < 5000) {} // 5 секунд
}

app.get('/fast', (req, res) => {
  res.send('Fast endpoint');
});

app.get('/slow', (req, res) => {
  slowSync(); // This blocks ALL requests
  res.send('Slow endpoint');
});

// Если кто-то вызовет /slow, то /fast также будет заблокирован на 5 секунд!

2. CPU-intensive операции

Для тяжелых операций используй Worker Threads или offload на отдельный процесс:

const { Worker } = require('worker_threads');

function heavyComputation(data) {
  return new Promise((resolve, reject) => {
    const worker = new Worker('./worker.js');
    worker.on('message', resolve);
    worker.on('error', reject);
    worker.postMessage(data);
  });
}

app.get('/compute', async (req, res) => {
  const result = await heavyComputation(largeData);
  res.json(result);
});

3. Monitoring Event Loop Lag

В production важно мониторить Event Loop для определения bottlenecks:

const ELG = require('event-loop-lag');

const lag = ELG();

setInterval(() => {
  console.log(`Event Loop Lag: ${lag()}ms`);
}, 1000);

Вывод

Event Loop — это сердце Node.js асинхронности. Понимание того как работает Event Loop, порядок выполнения Promises vs Callbacks, и как избежать блокирования — это необходимо для разработки high-performance Node.js приложений. Это критично при работе с масштабируемыми приложениями, где даже небольшие задержки приводят к значительным проблемам.