Как работает асинхронность в Node.js?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Асинхронность в Node.js: глубокий разбор
Node.js построен на асинхронности. Это его ДНК. Разберу пошагово, как всё устроено:
Event Loop — сердце Node.js
Event Loop — это бесконечный цикл, который обрабатывает события:
// Упрощённо:
while (eventLoop.waitForTask()) {
const task = eventLoop.nextTask();
task.execute();
}
Event Loop имеет несколько фаз:
- timers — выполняет callbacks из setTimeout/setInterval
- pending callbacks — I/O callbacks отложены с предыдущей итерации
- idle, prepare — внутренние операции
- poll — получает новые I/O события
- check — setImmediate
- close callbacks — очистка ресурсов
Callback Queue и Microtask Queue
Есть не одна очередь, а несколько:
// Microtask Queue (выше по приоритету)
Promise.then()
process.nextTick()
queueMicrotask()
// Callback Queue (ниже по приоритету)
setTimeout()
setInterval()
setImmediate()
Важно: Event Loop обрабатывает ВСЕ microtasks перед следующей фазой!
console.log('1'); // Синхронно
setTimeout(() => console.log('2'), 0); // Callback Queue
Promise.resolve().then(() => console.log('3')); // Microtask Queue
console.log('4'); // Синхронно
// Вывод: 1 4 3 2
Promises и async/await
Promises — это объекты, которые представляют значение, которое появится когда-то в будущем:
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('done!'), 1000);
});
promise.then(result => console.log(result));
async/await — синтаксический сахар над Promises:
async function fetchData() {
try {
const result = await promise;
console.log(result);
} catch (error) {
console.error(error);
}
}
libuv — нижний уровень
Node.js использует libuv — это C-библиотека, которая управляет асинхронными операциями:
Node.js ↓ V8 JavaScript Engine ↓
libuv (Event Loop) ↓
OS (Linux, macOS, Windows)
libuv:
- Управляет thread pool (по умолчанию 4 потока)
- Обрабатывает I/O операции (файлы, сеть)
- Реализует Event Loop
Blocking vs Non-blocking
Blocking — приложение ждёт:
const data = fs.readFileSync('file.txt'); // Блокирует
Non-blocking — приложение не ждёт:
fs.readFile('file.txt', (err, data) => {
// Обработка когда файл готов
});
Практические примеры
Проблема: CPU-heavy операции
// Плохо — блокирует Event Loop
app.get('/heavy', (req, res) => {
const result = heavyComputation();
res.json(result);
});
// Хорошо — используем Worker Threads
const { Worker } = require('worker_threads');
const worker = new Worker('computation.js');
worker.on('message', (result) => res.json(result));
Проблема: Callback Hell
// Плохо
fs.readFile('file1.txt', (err1, data1) => {
fs.readFile('file2.txt', (err2, data2) => {
fs.readFile('file3.txt', (err3, data3) => {
// Ужас!
});
});
});
// Хорошо
async function readFiles() {
const data1 = await fs.promises.readFile('file1.txt');
const data2 = await fs.promises.readFile('file2.txt');
const data3 = await fs.promises.readFile('file3.txt');
}
Ошибки в асинхронном коде
Проблема: забыли await
// Плохо
async function process() {
const data = fetchData(); // Забыли await!
console.log(data); // Promise, не данные
}
// Хорошо
async function process() {
const data = await fetchData();
console.log(data); // Реальные данные
}
Проблема: unhandled rejection
// Плохо
async function process() {
await riskyOperation(); // Ошибка не обработана
}
// Хорошо
async function process() {
try {
await riskyOperation();
} catch (error) {
console.error(error);
}
}
Performance Tips
- Избегай Zalgo pattern (непредсказуемое чередование sync/async)
- Группируй операции в batch для эффективности
- Следи за утечками памяти в Promises
- Профилируй Event Loop с инструментами типа Clinic.js
Асинхронность — это то, что делает Node.js мощным. Когда её понимаешь на глубоком уровне, код становится более эффективным и надёжным.