Event Loop с Promise.reject
Условие
Дан следующий код:
console.log(1);
setTimeout(() => console.log(2));
Promise.reject(3).catch(console.log);
new Promise(resolve => setTimeout(resolve)).then(() => console.log(4));
Promise.resolve(5).then(console.log);
console.log(6);
setTimeout(() => console.log(7), 0);
Определите порядок вывода и объясните почему.
Что проверяется
- Понимание Event Loop
- Работа с Promise.reject и catch
- Макрозадачи, порождающие микрозадачи
- Порядок выполнения setTimeout с нулевой задержкой
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Порядок вывода
1
6
3
5
2
4
7
Объяснение
Это типичная задача на понимание Event Loop в JavaScript. Разберу пошагово.
Основные концепции
Call Stack — стек синхронного кода
Microtask Queue — очередь микрозадач (Promises, queueMicrotask, MutationObserver)
Macrotask Queue — очередь макрозадач (setTimeout, setInterval, setImmediate, I/O)
Порядок исполнения
-
Synchronous code (Call Stack):
console.log(1); // Выводит: 1 setTimeout(...); // Добавляет в Macrotask Queue Promise.reject(3).. // Добавляет в Microtask Queue new Promise(...); // setTimeout создаёт новый Promise Promise.resolve(5).. // Добавляет в Microtask Queue console.log(6); // Выводит: 6 setTimeout(...); // Добавляет в Macrotask QueueВывод: 1, 6
-
Microtask Queue (после синхронного кода):
- Promise.reject(3).catch(console.log) → Выводит: 3
- Promise.resolve(5).then(console.log) → Выводит: 5
-
Macrotask Queue (после всех микрозадач):
- Первый setTimeout → Выводит: 2
- Внутри: new Promise с setTimeout
- Добавляет новую макрозадачу в очередь
- Второй setTimeout → Выводит: 7
- Микрозадачи из новой Promise (после первого setTimeout):
- .then(() => console.log(4)) → Выводит: 4
Визуализация Event Loop
┌─────────────────────────────────────────────────────┐
│ Шаг 1: Синхронный код (Call Stack) │
│ console.log(1) → 1 │
│ console.log(6) → 6 │
│ Добавлены в очереди: │
│ - Microtask: Promise.reject catch, Promise.resolve │
│ - Macrotask: 2 setTimeout │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Шаг 2: Microtask Queue │
│ Promise.reject(3).catch(console.log) → 3 │
│ Promise.resolve(5).then(console.log) → 5 │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Шаг 3: Первая Macrotask - setTimeout (2) │
│ console.log(2) → 2 │
│ Добавлена новая Promise с setTimeout │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Шаг 4: Вторая Macrotask - setTimeout (7) │
│ console.log(7) → 7 │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Шаг 5: Microtask из новой Promise │
│ .then(() => console.log(4)) → 4 │
└─────────────────────────────────────────────────────┘
Разбор каждой строки
console.log(1); // Выводит 1 сразу
setTimeout(() => console.log(2)); // Идёт в Macrotask Queue
Promise.reject(3).catch(console.log); // Идёт в Microtask Queue
new Promise(resolve => setTimeout(resolve))
.then(() => console.log(4));
// setTimeout внутри Promise идёт в Macrotask Queue
// .then() идёт в Microtask Queue, но выполнится после resolve
Promise.resolve(5).then(console.log); // Идёт в Microtask Queue
console.log(6); // Выводит 6 сразу
setTimeout(() => console.log(7), 0); // Идёт в Macrotask Queue
Почему именно такой порядок?
Event Loop правило:
- Выполняй весь синхронный код (Call Stack)
- Выполняй ВСЕ микрозадачи (Microtask Queue)
- Выполняй одну макрозадачу (Macrotask Queue)
- Повтори шаги 2-3
Ключевой момент: После каждой макрозадачи проверяются ВСЕ микрозадачи!
Альтернативный пример для закрепления
console.log('a');
setTimeout(() => {
console.log('b');
Promise.resolve().then(() => console.log('c'));
}, 0);
Promise.resolve().then(() => console.log('d'));
console.log('e');
Вывод: a, e, d, b, c
Почему так?
- Синхронно: a, e
- Микротаски: d
- Первая макротаска (setTimeout): b
- Микротаски из макротаски: c
Частые ошибки
❌ Неправильно: думать, что Promise.reject срабатывает после всех макротасок ✓ Правильно: микрозадачи (Promise) выполняются ДО следующей макротаски
❌ Неправильно: думать, что setTimeout(0) выполнится сразу ✓ Правильно: setTimeout(0) — минимальная задержка, идёт в очередь макротасок
Для запоминания
Мнемоника: Синхронное → Все микрозадачи → Одна макрозадача → Повторить
Примеры очередей:
- Microtask: Promise.then, Promise.catch, queueMicrotask, MutationObserver
- Macrotask: setTimeout, setInterval, setImmediate, I/O, UI rendering