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

В чем связь между Event Loop и Async/Await?

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

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

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

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

Event Loop и Async/Await в Node.js

Это одна из самых важных концепций в Node.js. Без понимания Event Loop невозможно писать правильный асинхронный код и отлаживать проблемы с производительностью.

Что такое Event Loop

Event Loop — это механизм, который позволяет Node.js выполнять асинхронный код несмотря на то, что JavaScript однопоточный.

В каждый момент времени выполняется только один кусок кода, но Event Loop управляет очередью задач и выполняет их в правильном порядке.

Визуализация Event Loop

┌───────────────────────┐
│     Call Stack        │  Текущее выполнение
│   (синхронный код)    │
└───────────────────────┘
         ▲
         │
┌────────┴───────────────────┐
│    EVENT LOOP              │
│  (бесконечный цикл)        │
│                            │
│  1. Проверить очередь      │
│  2. Выполнить задачу       │
│  3. Вернуться в 1          │
└────────┬───────────────────┘
         │
         ▼
┌───────────────────────────────────┐
│   Task Queues (несколько очередей)│
│                                   │
│  ├─ Microtasks (Promises, etc)    │
│  │                               │
│  ├─ Timers (setTimeout, etc)     │
│  │                               │
│  ├─ I/O operations (файлы, БД)   │
│  │                               │
│  └─ Callbacks                     │
└───────────────────────────────────┘

Порядок выполнения (очень важно!)

Event Loop выполняет задачи в строгом порядке:

1. СИНХРОННЫЙ КОД (Call Stack)
   ↓
2. MICROTASK QUEUE (Promises, async/await, queueMicrotask)
   ↓
3. TIMER QUEUE (setTimeout, setInterval)
   ↓
4. I/O QUEUE (файлы, сеть, БД)
   ↓
5. CHECK QUEUE (setImmediate)
   ↓
6. Вернуться к пункту 1

Пример с синхронным кодом

console.log('1: Start');

console.log('2: Middle');

console.log('3: End');

// Результат:
// 1: Start
// 2: Middle
// 3: End

// Всё выполняется в порядке сверху вниз, нет ничего асинхронного

Event Loop с callbacks

console.log('1: Start');

setTimeout(() => {
  console.log('2: Timeout callback');
}, 0);

console.log('3: End');

// Результат:
// 1: Start
// 3: End
// 2: Timeout callback

// Почему? Event Loop так работает:
// 1. Выполняет синхронный код: '1: Start' и '3: End'
// 2. Call Stack пуст
// 3. Проверяет Timer Queue
// 4. Находит callback для setTimeout
// 5. Выполняет: '2: Timeout callback'

Event Loop с Promises (Microtasks)

console.log('1: Start');

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

Promise.resolve()
  .then(() => {
    console.log('3: Promise 1');
  })
  .then(() => {
    console.log('4: Promise 2');
  });

console.log('5: End');

// Результат:
// 1: Start
// 5: End
// 3: Promise 1
// 4: Promise 2
// 2: Timeout

// Порядок выполнения:
// 1. Синхронный код: '1: Start' и '5: End'
// 2. Call Stack пуст
// 3. Event Loop проверяет MICROTASK очередь (Promises!)
// 4. Выполняет: '3: Promise 1' и '4: Promise 2'
// 5. ТОЛЬКО ПОТОМ проверяет Timer Queue
// 6. Выполняет: '2: Timeout'

Это критично понимать: Promises выполняются ДО setTimeout!

Async/Await это синтаксис для Promises

Async/Await — это просто красивый синтаксис для работы с Promises. Под капотом это всё ещё Promises!

// Способ 1: Promise.then
function getUser(id) {
  return fetch(`/api/users/${id}`)
    .then(res => res.json())
    .then(user => user);
}

// Способ 2: Async/Await (одно и то же!)
async function getUser(id) {
  const res = await fetch(`/api/users/${id}`);
  const user = await res.json();
  return user;
}

Когда ты пишешь await, это создаёт Promise и добавляет callback в Microtask Queue.

Пример: Event Loop с Async/Await

console.log('1: Start');

async function main() {
  console.log('2: Inside async');
  
  const result = await Promise.resolve('3: From promise');
  console.log(result);
  
  console.log('4: After await');
}

main();

console.log('5: End');

// Результат:
// 1: Start
// 2: Inside async
// 5: End
// 3: From promise
// 4: After await

// Порядок:
// 1. '1: Start' (синхронный)
// 2. Вызываем main(), выполняется '2: Inside async' (синхронный)
// 3. Встречаем await, он добавляет оставшуюся часть в Microtask Queue
// 4. '5: End' (синхронный)
// 5. Call Stack пуст, Event Loop проверяет Microtask Queue
// 6. Выполняет код после await: '3: From promise' и '4: After await'

Практический пример: Проблема с производительностью

// ❌ ПЛОХО: блокирует Event Loop
const processUsers = async (users) => {
  for (const user of users) {
    await updateUser(user); // ждёшь каждого!
  }
};

// Если 1000 пользователей, это займёт 1000 * время запроса

// ✅ ХОРОШО: параллельные запросы
const processUsers = async (users) => {
  await Promise.all(
    users.map(user => updateUser(user))
  );
};

// Все запросы идут одновременно!

Event Loop порядок в деталях

console.log('1: Script start');

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

Promise.resolve()
  .then(() => {
    console.log('3: Promise 1');
    setTimeout(() => {
      console.log('4: setTimeout inside promise');
    }, 0);
  })
  .then(() => {
    console.log('5: Promise 2');
  });

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

console.log('7: Script end');

// Результат:
// 1: Script start
// 7: Script end
// 3: Promise 1
// 5: Promise 2
// 2: setTimeout 1
// 6: setTimeout 2
// 4: setTimeout inside promise

// Объяснение:
// ЭТАП 1: Синхронный код
//   - '1: Script start'
//   - setTimeout #1 добавляется в Timer Queue
//   - Promise.then добавляется в Microtask Queue
//   - setTimeout #2 добавляется в Timer Queue
//   - '7: Script end'
//
// ЭТАП 2: Microtask Queue (Promises)
//   - '3: Promise 1'
//   - setTimeout (внутри promise) добавляется в Timer Queue
//   - '5: Promise 2'
//
// ЭТАП 3: Timer Queue (setTimeout)
//   - '2: setTimeout 1'
//   - '6: setTimeout 2'
//   - '4: setTimeout inside promise'

Async/Await и обработка ошибок

// Async/Await работает с Promises, поэтому:

async function fetchUser(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    const user = await res.json();
    return user;
  } catch (error) {
    console.error('Error:', error);
  }
}

// try-catch ловит ошибки из await (из Promises)

Практическая проблема: Race conditions

// ❌ ПРОБЛЕМА: race condition
let userData = null;

const loadUser = async (id) => {
  const user = await fetch(`/api/users/${id}`);
  userData = user; // ❌ Если запросов несколько, данные перезатирают друг друга
};

loadUser(1);
LoadUser(2); // Какой результат: 1 или 2?

// ✅ РЕШЕНИЕ: используй Promise.all или другие методы
const loadUsers = async (ids) => {
  const users = await Promise.all(
    ids.map(id => fetch(`/api/users/${id}`))
  );
  return users;
};

Как отлаживать Event Loop проблемы

const logWithTime = (msg) => {
  console.log(`${new Date().getTime()}: ${msg}`);
};

logWithTime('1: Start');

setTimeout(() => {
  logWithTime('2: setTimeout');
}, 100);

Promise.resolve()
  .then(() => {
    logWithTime('3: Promise');
  });

logWithTime('4: End');

// Вывод покажет временные метки и очередь выполнения

Итого: Event Loop и Async/Await

  1. Event Loop — это механизм для управления асинхронным выполнением кода
  2. Порядок выполнения строгий: синхронный код → Microtasks (Promises/async) → Timers → I/O
  3. Async/Await — это синтаксис для Promises, они используют Microtask Queue
  4. Promises БЫСТРЕЕ setTimeout потому что они в очереди выше по приоритету
  5. Await по сути создаёт Promise, который добавляется в Microtask Queue

Это знание критично для:

  • Оптимизации производительности
  • Избежания race conditions
  • Отладки странного поведения асинхронного кода
  • Понимания как работает Node.js внутри
В чем связь между Event Loop и Async/Await? | PrepBro