Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
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 приложений. Это критично при работе с масштабируемыми приложениями, где даже небольшие задержки приводят к значительным проблемам.