Події в JavaScript: як працює Event Loop

JavaScript — це мова програмування з однопотоковою природою, але при цьому вона вміє обробляти асинхронні операції. Це можливо завдяки Event Loop — механізму, який дозволяє JavaScript виконувати код без блокування основного потоку. У цьому дописі розглянемо, як саме працює Event Loop, чому він важливий і як правильно використовувати його особливості.

Основи роботи Event Loop

В основі роботи Event Loop лежать три основні складові:

Розглянемо приклад:

console.log("Початок");

setTimeout(() => {
  console.log("setTimeout");
}, 0);

Promise.resolve().then(() => {
  console.log("Promise");
});

console.log("Кінець");

Очікуваний порядок виконання:

  1. console.log("Початок") — виводиться одразу.
  2. setTimeout відкладає виконання колбека в Task Queue.
  3. Promise.resolve().then(...) додає колбек у Microtask Queue.
  4. console.log("Кінець") — виводиться одразу.
  5. Після завершення синхронного коду Event Loop перевіряє Microtask Queue — виконується console.log("Promise").
  6. Лише після цього виконується колбек з Task Queue — console.log("setTimeout").

Результат у консолі буде:

Початок
Кінець
Promise
setTimeout

Чому setTimeout(..., 0) не виконується миттєво?

setTimeout(..., 0) не означає, що функція виконається одразу. Він додає колбек у Task Queue, який обробляється лише після виконання всіх мікрозавдань.

Приклад, де це добре видно:

setTimeout(() => console.log("setTimeout"), 0);

for (let i = 0; i < 1e9; i++) {} // Блокуємо основний потік

console.log("Кінець циклу");

Тут setTimeout затримується, поки цикл не завершиться, навіть якщо стоїть 0 мілісекунд.

Використання queueMicrotask

Якщо потрібно виконати код раніше, ніж setTimeout, можна скористатися queueMicrotask:

queueMicrotask(() => console.log("Мікрозавдання"));
console.log("Основний код");

Результат:

Основний код
Мікрозавдання