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

Как работает асинхронность в Node.js?

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

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

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

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

Асинхронность в Node.js: глубокий разбор

Node.js построен на асинхронности. Это его ДНК. Разберу пошагово, как всё устроено:

Event Loop — сердце Node.js

Event Loop — это бесконечный цикл, который обрабатывает события:

// Упрощённо:
while (eventLoop.waitForTask()) {
  const task = eventLoop.nextTask();
  task.execute();
}

Event Loop имеет несколько фаз:

  1. timers — выполняет callbacks из setTimeout/setInterval
  2. pending callbacks — I/O callbacks отложены с предыдущей итерации
  3. idle, prepare — внутренние операции
  4. poll — получает новые I/O события
  5. check — setImmediate
  6. 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 мощным. Когда её понимаешь на глубоком уровне, код становится более эффективным и надёжным.