JS Tower
JavaScript & TypeScriptМодуль 5: Асинхронность

Event Loop

Как JavaScript выполняет код

Цель урока

В этом уроке ты научишься:

  • Понимать однопоточность JavaScript
  • Разбираться в Call Stack и очередях
  • Предсказывать порядок выполнения

Однопоточность

JavaScript выполняет код в одном потоке. Одновременно может выполняться только одна операция.

console.log("Первый");
console.log("Второй");
console.log("Третий");
// Всегда в этом порядке

Call Stack

Стек вызовов — структура, отслеживающая выполнение функций:

function first() {
  console.log("first");
  second();
}

function second() {
  console.log("second");
  third();
}

function third() {
  console.log("third");
}

first();

// Стек:
// 1. first() добавляется
// 2. console.log("first") выполняется
// 3. second() добавляется
// 4. console.log("second") выполняется
// 5. third() добавляется
// 6. console.log("third") выполняется
// 7. third() удаляется
// 8. second() удаляется
// 9. first() удаляется

Web APIs

Браузер предоставляет API для асинхронных операций:

  • setTimeout, setInterval
  • fetch
  • DOM события
  • и другие
console.log("Начало");

setTimeout(() => {
  console.log("Таймер");
}, 0);

console.log("Конец");

// Вывод:
// Начало
// Конец
// Таймер

Почему так?

Даже с задержкой 0мс, callback попадает в очередь и ждёт, пока стек освободится.


Task Queue (Macrotasks)

Очередь макрозадач — сюда попадают:

  • Callbacks от setTimeout, setInterval
  • События DOM
  • I/O операции
console.log("1");

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

console.log("4");

// Вывод: 1, 4, 2, 3

Microtask Queue

Очередь микрозадач — приоритетнее макрозадач:

  • Callbacks от Promise (.then, .catch, .finally)
  • queueMicrotask()
  • MutationObserver
console.log("1");

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

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

console.log("4");

// Вывод: 1, 4, 3, 2

Порядок выполнения

  1. Выполняется синхронный код (Call Stack)
  2. Выполняются все микрозадачи
  3. Выполняется одна макрозадача
  4. Повторяется с пункта 2
console.log("1");

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

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

console.log("6");

// Вывод: 1, 6, 4, 2, 3, 5

Практический пример

console.log("Скрипт начат");

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

Promise.resolve()
  .then(() => console.log("Promise 1"))
  .then(() => console.log("Promise 2"));

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

console.log("Скрипт завершён");

// Вывод:
// Скрипт начат
// Скрипт завершён
// Promise 1
// Promise 2
// setTimeout 1
// setTimeout 2

Блокировка Event Loop

Тяжёлые синхронные операции блокируют весь интерфейс:

// Плохо — блокирует на 5 секунд
function heavyTask() {
  let start = Date.now();
  while (Date.now() - start < 5000) {
    // блокируем
  }
  console.log("Готово");
}

heavyTask();
// Страница "зависает" на 5 секунд

Избегай блокировки

Разбивай тяжёлые задачи на части или используй Web Workers.


Итого

ОчередьПримерыПриоритет
Call StackСинхронный кодВысший
MicrotasksPromise, queueMicrotaskВысокий
MacrotaskssetTimeout, событияОбычный

Практика

Задание 1: Предскажи вывод

Задача: Определи порядок вывода.

Запустите код для проверки
Loading...
Ваш вывод:
Ожидаемый результат:
A
D
C
B

Задание 2: Сложный пример

Задача: Определи порядок вывода.

Запустите код для проверки
Loading...
Ваш вывод:
Ожидаемый результат:
5
2
4
1
3

Проверь себя

  1. Что такое Call Stack?
  2. Чем микрозадачи отличаются от макрозадач?
  3. Почему setTimeout(fn, 0) не выполняется сразу?