🪿 IT Птица · эталонная шпаргалка

Event Loop подробно: браузер + Node.js

За 30 секунд
МодельБраузер: tickМикро vs макро Задачи (браузер)Node: фазыnextTick + микро setImmediate vs setTimeoutЗадачи (Node)Браузер vs Node Кто в какую очередьЛовушкиВопросы
1

Общая модель

Один принцип и там, и там: движок исполняет синхронный код, хост поставляет асинхронные события в очереди.

Call stack— async →Web API / libuv — готово →Очереди— стек пуст →Event loopCall stack

Движок (V8)

Знает только язык: call stack, heap, промисы. Ни таймеров, ни файлов, ни DOM.

Хост

Браузер: Web API (таймеры, DOM, fetch). Node: libuv (таймеры, I/O, thread pool).

Очереди

Макро (задачи) + микро. В Node ещё nextTick-очередь и фазовые очереди.

Event loop

Пустой стек → переложить готовые колбэки в стек по строгим правилам.

Promise — это язык (ECMAScript), поэтому микротаски работают одинаково. А setTimeout/setImmediate/process.nextTick — это хост, поэтому у браузера и Node они разные.
2

Браузер: один tick browser

Модель из HTML-спеки. Проще, чем в Node — без фаз.

Взять одну макрозадачуиз очереди задач, выполнить целиком (синхронный скрипт — тоже задача)
Microtask checkpointвыгрести ВСЕ микрозадачи до конца (включая порождённые)
Update the rendering — если время кадраrAF → ResizeObserver/IntersectionObserver → style → layout → paint
↻ следующая макрозадача
Правило браузера: микроочередь выгребается после каждой макрозадачи и до рендера. Отсюда — микро всегда раньше следующей макро и раньше отрисовки.
3

Микрозадачи vs макрозадачи browser

🟢 Микро (раньше)🟡 Макро (позже)
Promise.then/catch/finally
продолжение после await
queueMicrotask()
MutationObserver
setTimeout, setInterval
DOM-события (click…)
MessageChannel, postMessage
сетевые колбэки
Особняком (не очередь задач, а шаг рендера): requestAnimationFrame, IntersectionObserver, ResizeObserver. requestIdleCallback — в простое.
4

Задачи «что выведется» 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 = микрозадача.

5

Node.js: фазы event loop (libuv) node

В Node loop делится на 6 фаз. У каждой — своя FIFO-очередь колбэков. Loop идёт по фазам по кругу.

timersколбэки setTimeout / setInterval, чей срок наступил
pending callbacksотложенные системные колбэки (напр. некоторые ошибки TCP)
idle / prepareтолько внутреннее (Node сам)
poll — сердце loopполучить новые I/O-события и выполнить их колбэки (чтение файла, сокет). Здесь loop блокируется в ожидании I/O, если нет таймеров и immediate
checkколбэки setImmediate
close callbacksсобытия 'close' (напр. socket.on('close'))
↻ снова с timers
Ключевое: между каждым колбэком (и между фазами) Node дренит сначала всю nextTick-очередь, потом все Promise-микротаски — только затем идёт дальше. Микрозадачи в Node не «в конце фазы», а после каждой операции.
6

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.
7

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 — далеко, на следующем круге.
8

Полный порядок в 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, timeout, immediate
Синхронный код (start, end) → nextTickpromise (микро) → фаза timers (timeout) → фаза check (immediate). timeout/immediate в main могут иногда поменяться местами.
9

Браузер vs Node — различия

БраузерNode.js
Структуразадачи + микро + рендер6 фаз (libuv) + nextTick + микро
process.nextTick❌ нет✅ приоритетная очередь
setImmediate❌ нет (нестандарт)✅ фаза check
Когда дренятся микропосле каждой макрозадачипосле каждого колбэка / между фазами
Рендеринг✅ шаг update rendering❌ нет (нет UI)
Web API / DOM❌ (свои модули fs/net/…)
Общее — язык: промисы и микротаски и порядок «микро раньше макро» одинаковы. Различается всё, что от хоста.
10

Кто в какую очередь

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)
11

Подводные камни

❌ Ошибки
  • «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
12

Реальные вопросы с собесов

Из нашей базы 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

Есть ли event loop в Node?
Да, свой (libuv, 6 фаз) — отличается от браузерного.
process.nextTick или Promise — кто раньше?
nextTick — у него отдельная приоритетная очередь.
setImmediate vs setTimeout(0)?
Внутри I/O — immediate раньше; в main — недетерминировано.
Чем опасен рекурсивный nextTick?
I/O starvation — loop не доходит до poll.
Микротаски в браузере и Node — одинаковы?
Сам механизм да (язык), но момент дренинга разный.
await блокирует поток?
Нет — ставит на паузу только свою async-функцию.
13

Граничит с темами

Промисы вглубь (all/race/allSettled/any, цепочки/ошибки) · браузерные Web API (таймеры/сеть/хранилища/observers — отдельная шпаргалка) · Web Workers · как браузер рендерит (шаг update rendering вглубь). Берём по запросу.