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

Как работает очередь в Event Loop?

2.3 Middle🔥 231 комментариев
#JavaScript Core

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

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

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

Event Loop: Очередь и асинхронность

Event Loop - это сердце асинхронности в JavaScript. Он управляет очередью задач и определяет, какая функция выполнится дальше.

Основная структура Event Loop

JavaScript имеет три основных компонента:

// 1. CALL STACK (Стек вызовов) - синхронный код
// 2. TASK QUEUE (Очередь задач) - асинхронные задачи
// 3. EVENT LOOP - управляет выполнением

// Процесс:
// 1. Event Loop проверяет Call Stack
// 2. Если Stack пуст, Event Loop берёт первую задачу из Queue
// 3. Если Stack не пуст, ждёт пока он освободится

Пример выполнения

console.log("1"); // синхронно

setTimeout(() => {
  console.log("2"); // асинхронно (Task Queue)
}, 0);

Promise.resolve().then(() => {
  console.log("3"); // микротаск (Microtask Queue)
});

console.log("4"); // синхронно

// Результат:
// 1
// 4
// 3
// 2

// Почему так?
// 1. console.log("1") выполняется сразу (Call Stack)
// 2. setTimeout отправляется в Task Queue
// 3. Promise.then отправляется в Microtask Queue
// 4. console.log("4") выполняется сразу (Call Stack)
// 5. Event Loop видит пустой Stack, проверяет Microtask Queue (есть!)
// 6. Выполняет console.log("3")
// 7. Event Loop видит пустой Stack, проверяет Task Queue (есть!)
// 8. Выполняет console.log("2")

Две очереди: Task Queue и Microtask Queue

Task Queue (Макротаски)

  • setTimeout, setInterval, setImmediate
  • UI рендеринг
  • Обработчики событий (click, scroll и т.д.)

Microtask Queue (Микротаски)

  • Promise.then, Promise.catch, Promise.finally
  • MutationObserver
  • queueMicrotask()
console.log("1"); // Stack

setTimeout(() => {
  console.log("2"); // Task Queue (макротаска)
}, 0);

Promise.resolve()
  .then(() => console.log("3")) // Microtask Queue
  .then(() => console.log("4")); // Microtask Queue

console.log("5"); // Stack

// Результат:
// 1
// 5
// 3
// 4
// 2

// Порядок приоритета:
// 1. Синхронный код (Call Stack) -> 1, 5
// 2. Все микротаски -> 3, 4
// 3. Первая макротаска -> 2
// 4. Если есть ещё макротаски, повторить с шага 2

Подробный пример: Promise vs setTimeout

const promise = Promise.resolve("Promise");
const timeout = setTimeout(() => console.log("setTimeout"), 0);

promise.then((value) => {
  console.log(value);
  clearTimeout(timeout); // Попытка очистить setTimeout
});

// Результат:
// Promise
// setTimeout

// Почему setTimeout всё ещё выполнится?
// Promise.then это микротаска - выполняется ДО setTimeout (которая макротаска)
// Но когда мы в Promise.then пытаемся clearTimeout, setTimeout уже в Task Queue
// ClearTimeout не может удалить задачу, которая уже запланирована на выполнение

Визуальное представление

Call Stack           <- Выполняется первым (синхронный код)
       |
       v
  Event Loop проверяет
       |
       v
Microtask Queue      <- Выполняется вторым (Promise, MutationObserver)
       |
       v
   Stack пуст?
       |
       v
Task Queue           <- Выполняется третьим (setTimeout, setInterval)
       |
       v
  Вернуться в Call Stack

Практический пример: Сложный сценарий

console.log("Start");

setTimeout(() => {
  console.log("setTimeout 1");
  Promise.resolve().then(() => console.log("Promise inside setTimeout"));
}, 0);

Promise.resolve()
  .then(() => {
    console.log("Promise 1");
    setTimeout(() => console.log("setTimeout inside Promise"), 0);
  })
  .then(() => console.log("Promise 2"));

console.log("End");

// Результат:
// Start
// End
// Promise 1
// Promise 2
// setTimeout 1
// Promise inside setTimeout
// setTimeout inside Promise

// Объяснение:
// 1. console.log("Start") - Stack
// 2. setTimeout -> Task Queue
// 3. Promise.then -> Microtask Queue
// 4. console.log("End") - Stack
// 5. Stack пуст, выполняем микротаски: Promise 1, Promise 2
// 6. Во время Promise 2 создаётся ещё один setTimeout -> Task Queue
// 7. Выполняем макротаски: setTimeout 1
// 8. Во время setTimeout 1 создаётся Promise -> Microtask Queue
// 9. Выполняем все оставшиеся микротаски: Promise inside setTimeout
// 10. Выполняем следующую макротаску: setTimeout inside Promise

Оптимизация и best practices

1. Избегай цепочек setTimeout

// ❌ Плохо - медленно
setTimeout(() => {
  setTimeout(() => {
    setTimeout(() => {
      console.log("Done");
    }, 0);
  }, 0);
}, 0);

// ✅ Хорошо - используй Promise
Promise.resolve()
  .then(() => console.log("Done"))
  .catch(console.error);

2. Используй requestAnimationFrame для UI обновлений

// ❌ Плохо - может блокировать рендеринг
setInterval(() => {
  updateDOM();
}, 16);

// ✅ Хорошо - синхронизирован с браузером
function updateUI() {
  requestAnimationFrame(() => {
    updateDOM();
    updateUI(); // Рекурсивный вызов
  });
}
updateUI();

3. Правильное использование Promise и async/await

// ✅ Хорошо - async/await читаемее и безопаснее
async function fetchData() {
  try {
    const response = await fetch("/api/data");
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error("Ошибка:", error);
  }
}

// Это еквивалентно:
// Promise.then().catch()
// но более понятно

Ключ к пониманию Event Loop: сначала выполняется синхронный код, потом микротаски, потом макротаски.