- JS однопоточный (call stack). Асинхронность даёт хост: браузер (Web API) или Node (libuv).
- Браузер: 1 макротаска → все микротаски → (рендер). И так по кругу.
- Node: 6 фаз (timers → pending → poll → check → close), а между каждым колбэком дренятся
nextTick-очередь, затем Promise-микротаски. - Node-приоритет:
process.nextTickраньшеPromise.then. setImmediatevssetTimeout(0): внутри I/O — immediate всегда раньше; в main — как повезёт.- Микро всегда обгоняют макро.
await= микрозадача. Рекурсивные микро/nextTick → starvation.
Общая модель
Один принцип и там, и там: движок исполняет синхронный код, хост поставляет асинхронные события в очереди.
Движок (V8)
Знает только язык: call stack, heap, промисы. Ни таймеров, ни файлов, ни DOM.
Хост
Браузер: Web API (таймеры, DOM, fetch). Node: libuv (таймеры, I/O, thread pool).
Очереди
Макро (задачи) + микро. В Node ещё nextTick-очередь и фазовые очереди.
Event loop
Пустой стек → переложить готовые колбэки в стек по строгим правилам.
setTimeout/setImmediate/process.nextTick — это хост, поэтому у браузера и Node они разные.Браузер: один tick browser
Модель из HTML-спеки. Проще, чем в Node — без фаз.
Микрозадачи vs макрозадачи browser
| 🟢 Микро (раньше) | 🟡 Макро (позже) |
|---|---|
Promise.then/catch/finallyпродолжение после awaitqueueMicrotask()MutationObserver |
setTimeout, setIntervalDOM-события (click…) MessageChannel, postMessageсетевые колбэки |
requestAnimationFrame, IntersectionObserver, ResizeObserver. requestIdleCallback — в простое.Задачи «что выведется» browser
Базовая
console.log('1');
setTimeout(() => console.log('2')); // макро
Promise.resolve().then(() => console.log('3')); // микро
console.log('4');
→ 1, 4, 3, 2 · синхронный код → все микро → следующая макро.
async/await
async function a(){ console.log('a1'); await b(); console.log('a2'); }
function b(){ console.log('b'); }
console.log('start'); a(); console.log('end');
→ start, a1, b, end, a2 · await b() зовёт b() синхронно, а код после await = микрозадача.
Node.js: фазы event loop (libuv) node
В Node loop делится на 6 фаз. У каждой — своя FIFO-очередь колбэков. Loop идёт по фазам по кругу.
setTimeout / setInterval, чей срок наступилsetImmediate'close' (напр. socket.on('close'))nextTick-очередь, потом все Promise-микротаски — только затем идёт дальше. Микрозадачи в Node не «в конце фазы», а после каждой операции.process.nextTick и микротаски node
Две отдельные очереди, которых нет в браузере, с чётким приоритетом.
| Очередь | Что кладёт | Приоритет |
|---|---|---|
nextTick-очередь | process.nextTick() | 🥇 выше всех — дренится первой |
| микротаски | Promise.then, queueMicrotask, await | 🥈 после nextTick |
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
// → nextTick, promise (nextTick раньше промиса)
process.nextTick (или бесконечные промисы) выгребается до перехода к I/O → loop не доходит до poll → I/O starvation, сервер «висит». setImmediate так не делает — он уступает loop.setImmediate vs setTimeout(0) node
Классический подвох Node-собеса.
В main-модуле
Порядок не гарантирован — зависит от того, успел ли таймер «дозреть» к первой итерации. Может быть и так, и эдак.
setTimeout(()=>console.log('timeout'),0);
setImmediate(()=>console.log('immediate'));
// порядок недетерминированВнутри I/O-колбэка
immediate ВСЕГДА раньше. После фазы poll (где мы в I/O) сразу идёт check (immediate), а timers — только на следующем витке.
fs.readFile('f', () => {
setTimeout(()=>console.log('timeout'),0);
setImmediate(()=>console.log('immediate'));
});
// → immediate, timeout (стабильно)timers → poll → check. Ты в poll (I/O) → check (immediate) рядом, справа; timers — далеко, на следующем круге.Полный порядок в Node node
console.log('start');
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
console.log('end');
Синхронный код (
start, end) → nextTick → promise (микро) → фаза timers (timeout) → фаза check (immediate). timeout/immediate в main могут иногда поменяться местами.Браузер vs Node — различия
| Браузер | Node.js | |
|---|---|---|
| Структура | задачи + микро + рендер | 6 фаз (libuv) + nextTick + микро |
process.nextTick | ❌ нет | ✅ приоритетная очередь |
setImmediate | ❌ нет (нестандарт) | ✅ фаза check |
| Когда дренятся микро | после каждой макрозадачи | после каждого колбэка / между фазами |
| Рендеринг | ✅ шаг update rendering | ❌ нет (нет UI) |
| Web API / DOM | ✅ | ❌ (свои модули fs/net/…) |
Кто в какую очередь
| API | Куда |
|---|---|
process.nextTick node | 🥇 nextTick-очередь (раньше промисов) |
Promise.then / await / queueMicrotask | 🟢 микрозадача |
MutationObserver browser | 🟢 микрозадача |
setTimeout / setInterval | 🟡 макро (в Node — фаза timers) |
setImmediate node | 🟡 фаза check |
| I/O-колбэки (fs, net) node | 🟡 фаза poll |
DOM-событие, MessageChannel browser | 🟡 макрозадача |
requestAnimationFrame browser | 🎞 шаг рендера (перед paint) |
Подводные камни
- «
setTimeout(0)= сразу» — нет, после стека + микро - Долгий синхронный код блокирует весь loop
- Рекурсивные микро /
nextTick→ starvation (браузер — render, Node — I/O) - Полагаться на порядок
setImmediate/setTimeoutв main var+setTimeoutв цикле → последнее значение
- Порядок: sync → nextTick(Node) → микро → макро/фазы
- Тяжёлое — в Worker / дробить
- Не плодить микро рекурсивно; тяжёлое — через
setImmediate - Детерминизм immediate>timeout — только внутри I/O
letв циклах с замыканиями
// Классика: var → 3,3,3 ; let → 0,1,2
for (var i=0;i<3;i++) setTimeout(()=>console.log(i));
// колбэк сработает после цикла, var одна на всех → 3,3,3
Реальные вопросы с собесов
Из нашей базы 2000+ собесов.
• Что такое Event Loop, где ещё работает кроме браузера? — МТС, Сбер, PetraStudio
• Event Loop подробно, разница браузер vs Node.js, Web API — АФЛТ Системс, Сбер, F6
• Стандартная задача на порядок микро/макро — JSA Group, Wildberries WB, BetBoom, Норникель
• Задача: вложенные setTimeout/Promise, var в цикле — РУСАЛ, Queen Interactive, Sports.ru
• setTimeout/setInterval — что возвращает, отличия — Т-банк, Онлайн Гимназия, Баланс-Платформа
Каверзные follow-up
Граничит с темами
all/race/allSettled/any, цепочки/ошибки) · браузерные Web API (таймеры/сеть/хранилища/observers — отдельная шпаргалка) · Web Workers · как браузер рендерит (шаг update rendering вглубь). Берём по запросу.