Як працює 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 додатків та уникнення проблем з продуктивністю.