Середнійevent-loopasynchronouscallbacksperformance

Як працює Event Loop в Node.js?

Детальний розбір Event Loop - серця асинхронності в Node.js

Як працює Event Loop в Node.js?

Event Loop - це серце асинхронної природи Node.js. Він дозволяє Node.js виконувати неблокуючі I/O операції, незважаючи на те, що JavaScript є однопотоковим.

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

Event Loop складається з кількох фаз, кожна з яких має свою чергу колбеків:

┌───────────────────────────┐
┌─>│           timers          │  ← setTimeout, setInterval
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │  ← I/O callbacks
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │  ← внутрішнє використання
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           poll            │  ← отримання нових I/O подій
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           check           │  ← setImmediate callbacks
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │  ← socket.on('close', ...)
   └───────────────────────────┘

Фази Event Loop

1. Timers

Виконує колбеки заплановані через setTimeout() та setInterval():

console.log('Start');

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

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

console.log('End');

// Вивід:
// Start
// End
// Timer 1
// Timer 2

2. Pending Callbacks

Виконує I/O колбеки, відкладені з попередньої ітерації циклу.

3. Poll

Найважливіша фаза:

  • Отримує нові I/O події
  • Виконує I/O-зв'язані колбеки
const fs = require('fs');

console.log('Start');

fs.readFile('./file.txt', (err, data) => {
  console.log('File read complete');
});

console.log('End');

// Вивід:
// Start
// End
// File read complete

4. Check

Виконує setImmediate() колбеки:

console.log('Start');

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

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

console.log('End');

// Вивід може варіюватися:
// Start
// End
// setTimeout
// setImmediate

process.nextTick() та Microtasks

process.nextTick() та Promise callbacks мають вищий пріоритет:

console.log('Start');

setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));

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

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

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

console.log('End');

// Вивід:
// Start
// End
// nextTick 1
// nextTick 2
// Promise 1
// setTimeout
// setImmediate

Приклад складного виконання

const fs = require('fs');

console.log('=== Start ===');

// Timers
setTimeout(() => console.log('1: setTimeout'), 0);
setInterval(() => console.log('2: setInterval'), 50);

// Immediate
setImmediate(() => console.log('3: setImmediate'));

// I/O
fs.readFile(__filename, () => {
  console.log('4: fs.readFile');
  
  setTimeout(() => console.log('5: setTimeout inside I/O'), 0);
  setImmediate(() => console.log('6: setImmediate inside I/O'));
  
  process.nextTick(() => console.log('7: nextTick inside I/O'));
});

// nextTick та Promise
process.nextTick(() => console.log('8: nextTick'));
Promise.resolve().then(() => console.log('9: Promise'));

console.log('=== End ===');

Блокування Event Loop

Уникайте блокування Event Loop:

// ❌ Погано - блокує Event Loop
function heavyComputation() {
  const start = Date.now();
  while (Date.now() - start < 5000) {
    // Важкі обчислення
  }
}

heavyComputation();
console.log('Це виконається через 5 секунд');

// ✅ Добре - неблокуючий підхід
function heavyComputationAsync(callback) {
  const start = Date.now();
  
  function compute() {
    const now = Date.now();
    if (now - start < 5000) {
      // Виконуємо частину роботи
      setImmediate(compute);
    } else {
      callback();
    }
  }
  
  setImmediate(compute);
}

heavyComputationAsync(() => {
  console.log('Важкі обчислення завершені');
});

console.log('Це виконається одразу');

Моніторинг Event Loop

// Вимірювання затримки Event Loop
function measureEventLoopLag() {
  const start = process.hrtime.bigint();
  
  setImmediate(() => {
    const lag = Number(process.hrtime.bigint() - start) / 1e6;
    console.log(`Event Loop lag: ${lag.toFixed(2)}ms`);
  });
}

setInterval(measureEventLoopLag, 1000);

Розуміння Event Loop критично важливе для написання ефективних Node.js додатків та уникнення проблем з продуктивністю.