Event Loop
Что такое event loop?
Event loop — механизм, который координирует выполнение кода, управляя очередями задач.
Схема работы:
Call Stack
↓ (пуст?)
Microtask Queue → выполнить все
↓
Macrotask Queue → взять одну задачу
↓
Microtask Queue → выполнить все
↓
... и так по кругу
JavaScript однопоточный — в каждый момент выполняется только одна задача.
Что такое call stack?
Стек вызовов — структура данных LIFO (последний вошёл — первый вышел).
function a() { b(); }
function b() { c(); }
function c() { console.log('hello'); }
a();
// Call stack:
// c() ← выполняется
// b()
// a()
// main()Пока стек не очищен — никакие колбэки из очередей не выполняются.
Что относится к microtasks, а что к macrotasks?
Microtasks (высокий приоритет):
Promise.then / .catch / .finallyqueueMicrotask(fn)MutationObserverasync/await(под капотом — Promise)
Macrotasks (низкий приоритет):
setTimeoutsetIntervalsetImmediate(Node.js)- UI-события (click, input)
Каков порядок выполнения sync → microtasks → macrotasks?
Алгоритм:
- Выполнить синхронный код (call stack).
- Дренировать очередь microtasks полностью (включая новые microtasks, добавленные в процессе).
- Взять одну macrotask из очереди.
- Снова дренировать microtasks.
- Повторить с шага 3.
Ключевое: microtasks дренируются целиком после каждой macrotask, а не по одной.
Почему Promise выполняется раньше setTimeout(…, 0)?
setTimeout(() => console.log('timeout'), 0); // macrotask
Promise.resolve().then(() => console.log('promise')); // microtask
// Вывод:
// promise ← microtask выполняется раньше
// timeout ← macrotask выполняется послеsetTimeout(..., 0) — не «немедленно», а «добавить в macrotask queue как можно скорее».
Классический пример с подвохом
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve()
.then(() => console.log('3'))
.then(() => console.log('4'));
console.log('5');| Шаг | Что происходит | Вывод |
|---|---|---|
| sync | console.log('1') | 1 |
| sync | Регистрируется setTimeout → macrotask | — |
| sync | Регистрируется Promise.then | — |
| sync | console.log('5') | 5 |
| microtask | .then(() => '3') | 3 |
| microtask | .then(() => '4') | 4 |
| macrotask | setTimeout callback | 2 |
Итого: 1 → 5 → 3 → 4 → 2
Что важно помнить про очередь microtasks?
Если microtask создаёт новую microtask — она тоже выполняется до следующей macrotask.
Promise.resolve()
.then(() => {
console.log('A');
return Promise.resolve();
})
.then(() => console.log('B'));
setTimeout(() => console.log('C'), 0);
// A → B → CБесконечные цепочки промисов могут заблокировать выполнение macrotasks.
Как коротко ответить про event loop на собеседовании?
JavaScript — однопоточный язык. Все задачи выполняются через call stack. После выполнения синхронного кода движок дренирует очередь microtasks (Promise, MutationObserver), затем берёт одну macrotask (setTimeout, setInterval), снова дренирует microtasks — и так по кругу.
В чём разница между requestAnimationFrame и setTimeout?
requestAnimationFrame (rAF) | setTimeout / setInterval | |
|---|---|---|
| Привязка | К циклу отрисовки браузера (обычно ~60 Гц, может совпадать с дисплеем) | К очереди таймеров (macrotask), не синхронизирован с кадром |
| Пауза вкладки | Часто приостанавливается в фоне — экономия CPU | Может продолжать тикать (зависит от браузера и минимальной задержки) |
| Типичное использование | Анимации, измерение перед следующей отрисовкой | Произвольная отложенная логика, поллинг, дебаунс «не привязанный к кадру» |
| Аргумент времени | Один колбэк на кадр; точное «через N мс» не задаётся | Явная задержка в мс (реальная не гарантирована) |
Для собеса: для плавной анимации и обновления визуала перед paint предпочтительнее rAF;
setTimeout(0)— не гарантия «сразу после текущего кадра», а отложенная macrotask.
См. также
- event-loop (вопросы) — задачи «что будет в консоли»
- js-objects-async — промисы и microtask queue