Микрозадачи против макрозадач: порядок
За оборот цикла выполняется одна макрозадача; вся очередь микрозадач осушается после каждой макрозадачи и каждый раз, когда пустеет стек JS. Микрозадача, поставившая микрозадачу, выполняется в том же осушении — корень starvation. Плюс порядок process.nextTick в Node.
Джуниор просит вас «починить баг», где это печатает 1 4 3 2 вместо 1 2 3 4. Никакого бага нет. Этот порядок — контракт: колбэк setTimeout ждёт своей очереди как макрозадача, пока Promise.then прыгает вперёд как микрозадача. Если вы не можете предсказать это чередование на лету, вы не сможете рассуждать о гонке в проде — и уж точно не объясните, почему тугая цепочка Promise только что заморозила вкладку.
Две очереди, два ритма
Предыдущий урок зафиксировал, кто чем владеет: хост владеет циклом и очередями макрозадач; движок владеет очередью микрозадач. Этот урок делает порядок точным, потому что две очереди осушаются с совершенно разными ритмами.
- Макрозадачи (спецификация называет их просто tasks) — за оборот цикла выполняется ровно одна. Источники: колбэки
setTimeout/setInterval, колбэки завершения I/O, сообщенияMessageChannel/postMessage, сработавшие DOM-события, разобранные куски HTML. Выполнив одну, цикл не запускает следующую макрозадачу немедленно. - Микрозадачи — вся очередь осушается после каждой макрозадачи и снова каждый раз, когда пустеет стек вызовов JavaScript. Источники: реакции
Promise.then/.catch/.finally, jobs отqueueMicrotask, продолженияawait, колбэкиMutationObserver.
Асимметрия — это вся суть. Макрозадачи нормированы — одна за оборот — чтобы цикл мог рендерить и обрабатывать ввод между ними. Микрозадачи исчерпывающи — осушаются до пустоты — чтобы целостная единица асинхронного продолжения (цепочка Promise) завершилась атомарно прежде, чем случится что-либо ещё. Браузерный обзор (browser/01-event-loop/04-microtask-starvation) показывает этот режим отказа на уровне рантайма; здесь мы привязываем его к механике очередей.
Канонический пример порядка
console.log("1: sync start");
setTimeout(() => console.log("4: macrotask (setTimeout)"), 0);
Promise.resolve()
.then(() => console.log("3a: microtask one"))
.then(() => console.log("3b: microtask two"));
queueMicrotask(() => console.log("3c: queueMicrotask"));
console.log("2: sync end");
// Вывод: 1 → 2 → 3a → 3c → 3b → 4Пройдём точно. Синхронный скрипт и есть текущая макрозадача. Он сперва исполняется до конца: 1, затем 2. По пути он запланировал макрозадачу (setTimeout) и микрозадачи (.then и queueMicrotask). Когда стек скрипта пустеет, движок осушает очередь микрозадач в порядке FIFO: первый .then (3a) и queueMicrotask (3c) были поставлены во время синхронного прогона, поэтому идут первыми; обработчик 3a возвращается, что теперь урегулирует сцепленный Promise и ставит 3b — добавленный в то же осушение, — поэтому 3b выполняется до конца осушения. Только когда очередь микрозадач наконец пуста, хост выбирает следующую макрозадачу: 4.
- Макрозадач за оборот цикла
- ровно 1
- Микрозадач за чекпоинт
- все, до пустоты
- Порядок осушения внутри очереди
- FIFO
- Рендер может вклиниться между
- только макрозадачами
- queueMicrotask против Promise.then
- одна очередь
- Node: process.nextTick против Promise
- nextTick первым
Почему микрозадача, ставящая микрозадачу, опасна
Поскольку чекпоинт микрозадач осушает до пустоты, микрозадача, поставившая другую микрозадачу до своего возврата, обрабатывается в том же осушении, а не в следующем. Нет границы «следующего оборота», к которой можно сбежать. Повторите это — и очередь никогда не пустеет:
function spin() {
Promise.resolve().then(spin); // перевзводится внутри того же чекпоинта
}
spin(); // замерзает: чекпоинт никогда не завершается, движок никогда не возвращаетсяЭто microtask starvation (голодание микрозадач). Хост никогда не получает поток назад (движок застрял внутри PerformMicrotaskCheckpoint из урока 1), поэтому ни одна макрозадача не выполняется, ни один кадр не рисуется, ввод не обрабатывается. Сравните с самозацикленной макрозадачей — setTimeout(spin, 0) — которая перевзводится на очереди хоста, поэтому движок возвращается между итерациями и страница остаётся отзывчивой (просто занятой). Лекарство от starvation — всегда вставить в цепочку yield на уровне макрозадачи.
Специфика Node: process.nextTick и пофазное осушение
Если вы читаете Node-кодовую базу и process.nextTick срабатывает раньше Promise.resolve().then — это не баг и не причуда, а намеренная вторая очередь до Promise. Node усложняет картину двумя дополнительными правилами. Во-первых, колбэки process.nextTick выполняются в очереди, которая отдельна от очереди микрозадач Promise и осушается до неё. Поэтому в Node process.nextTick всегда опережает Promise.resolve().then. Во-вторых, цикл Node работает фазами (timers → pending callbacks → poll → check → close), и исторически Node осушал микрозадачи между фазами, а не после каждого отдельного колбэка — хотя начиная с Node 11 он осушает микрозадачи после каждого отдельного колбэка макрозадачи, приближаясь к браузерной семантике.
// В Node:
setTimeout(() => console.log("timeout"), 0); // фаза timers (макрозадача)
setImmediate(() => console.log("immediate")); // фаза check (макрозадача)
Promise.resolve().then(() => console.log("promise")); // микрозадача
process.nextTick(() => console.log("nextTick")); // очередь nextTick — первая
// Порядок: nextTick → promise → (timeout|immediate, порядок между этими
// двумя не гарантирован из главного модуля)Практический сеньорный вывод: никогда не полагайтесь на порядок setTimeout против setImmediate с верхнего уровня, но полагайтесь на то, что process.nextTick выполняется до микрозадач Promise. И осторожно: у process.nextTick та же опасность starvation, что и у цикла микрозадач — рекурсивный nextTick морит голодом цикл фаз ровно так же, как рекурсивный .then морит браузер.
Внутри колбэка `setTimeout` вы запускаете `Promise.resolve().then(work)` и также планируете ещё один `setTimeout(other, 0)`. В каком порядке выполнятся `work` и `other`?
В Node вы планируете `process.nextTick(a)` и `Promise.resolve().then(b)` из одного синхронного блока. Что выполнится первым и почему?
Расставьте вывод канонического примера: синхронный скрипт планирует один setTimeout, цепочку .then из двух звеньев и один queueMicrotask. Перетащите строки лога в порядке их печати.
- 1 1: sync start (синхронно, текущая макрозадача)
- 2 2: sync end (синхронно, всё ещё та же макрозадача)
- 3 3a: первый обработчик .then (микрозадача, поставлена при синхронном прогоне)
- 4 3c: queueMicrotask (микрозадача, поставлена при синхронном прогоне)
- 5 3b: второй обработчик .then (микрозадача, поставлена 3a в том же осушении)
- 6 4: колбэк setTimeout (следующая макрозадача, после осушения очереди)
▸Граничные случаи
Продолжения await — тоже микрозадачи, поэтому подчиняются тому же правилу исчерпывающего осушения. Функция, которая await’ит в цикле, будет чередовать свои возобновления с каждой другой ожидающей микрозадачей прежде, чем выполнится любая макрозадача. Вот почему смешивание await внутри горячего, ощущающегося синхронным цикла может тихо отложить рендер гораздо дольше, чем вы ожидаете — await’ы держат очередь микрозадач непустой. Урок 04 делает отображение await-в-микрозадачу явным.
- 01Сформулируйте точное правило осушения для макрозадач против микрозадач и назовите источники каждой.
- 02Проследите, почему `1, setTimeout(4), Promise.then(3a).then(3b), queueMicrotask(3c), 2` печатает 1 2 3a 3c 3b 4.
- 03Чем порядок Node отличается от браузера и каково практическое правило?
Макрозадачи и микрозадачи осушаются в противоположных ритмах. За оборот цикла выполняется ровно одна макрозадача — setTimeout, завершения I/O, message-события, DOM-события — после чего цикл может рендерить и обрабатывать ввод. Вся очередь микрозадач, напротив, осушается FIFO до пустоты после каждой макрозадачи и каждый раз, когда пустеет стек JS — реакции Promise, queueMicrotask, продолжения await, MutationObserver. Поскольку осушение исчерпывающее, микрозадача, поставившая другую микрозадачу, выполняется в том же осушении, а не в более позднем обороте; повторённое, это microtask starvation, которое замораживает страницу, потому что движок никогда не завершает чекпоинт и не возвращает поток хосту. Самозацикленная макрозадача, напротив, возвращает поток каждый оборот и остаётся отзывчивой. Node добавляет очередь process.nextTick, осушаемую до очереди Promise (поэтому nextTick выигрывает), и пофазный цикл; начиная с Node 11 микрозадачи осушаются после каждого колбэка макрозадачи, близко к браузерному поведению. Канонический пример порядка — 1 2 3a 3c 3b 4 — это мышечная память, нужная каждому сеньору, чтобы рассуждать об асинхронных гонках. Теперь, когда встретишь вкладку, зависшую без видимой нагрузки на CPU, — первым делом спроси: не крутится ли что-то в очереди микрозадач и не отдаёт ли поток хосту?
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
встречается в184
- Почему GraphQL получает N+1junior
- Механика DataLoader: батчинг на границе тикаmiddle
- Контракты batch-функции: порядок, формы, ошибкиmiddle
- Federation и lookahead: батчинг за пределами DataLoadermiddle
- Защита сложности запросов: depth, cost, persisted queriesmiddle
- Senior GraphQL API: scheduling-контракт, изоляция арендаторов, наблюдаемостьsenior
- Зачем идемпотентность: безопасные retryjunior
- Серверный state machine: четыре состояния idempotency keymiddle
- Outbox и inbox: effectively-once через dual-write границуmiddle
- Конкурентность и архитектура кеша для идемпотентности на масштабеsenior
- Наблюдаемость, production-инциденты и дизайн для глобального масштабаsenior
- Event loop: один поток, три очередиjunior
- Задачи, микрозадачи и scheduler.yield()middle
- Голодание микрозадач, длинные задачи и LoAFsenior
- Event loop Node.js: фазы, nextTick и задержка циклаsenior
- React, Vue и наблюдаемость INP в продакшенеsenior
- Render pipeline: шесть стадий от байтов до пикселейjunior
- Цена стадий и модель процесса рендерераmiddle
- Инвалидация, dirty-биты и containmiddle
- Слои композитора: продвижение, перекрытие и память GPUmiddle
- Флейм-стрип DevTools и жизненный цикл кадраmiddle
- Layout thrash: форсированная синхронная компоновкаsenior
- BeginMainFrame, анимации на потоке compositor и память GPUsenior
- Observability в проде: LoAF, INP и полная поверхность атакиsenior
- Что такое V8 и почему производительность различается в 100 разjunior
- Четырёхуровневый JIT-конвейер V8 и профилированная тиеризацияmiddle
- Hidden classes, деревья переходов и расположение в памятиmiddle
- Inline caches, состояния IC и деоптимизацияmiddle
- Orinoco GC: параллельный scavenger, конкурентная разметка и барьеры записиmiddle
- Спекулятивный движок TurboFan и ловушка deopt-loopsenior
- V8 в production: Isolates, сжатие указателей и реальные аварииsenior
- Жизненный цикл service worker и стратегии кешированияmiddle
- Граничные случаи service worker: version skew, долговременность и ловушка навигацииsenior
- Что делает реконсилер: render vs commitjunior
- Объект fiber и дерево с двойной буферизациейmiddle
- Чистота фазы render и подшаги фазы commitmiddle
- Реконсиляция: эвристики диффа и ловушка ключейmiddle
- Приоритетные lanes, time-slicing и useTransitionmiddle
- Bailout, мемоизация и tearingsenior
- React Profiler, компилятор и продакшн-наблюдаемостьsenior
- Стратегии рендеринга: SSG, SSR, ISR, streaming и гидратацияjunior
- SSG, SSR, ISR, streaming и RSC — как работает каждая стратегияmiddle
- Цена гидратации: selective, progressive, острова, resumabilitymiddle
- Hydration mismatch: причины, обнаружение и правило детерминизмаsenior
- RSC, стратегия на маршрут и production-наблюдаемостьsenior
- Core Web Vitals: что измеряют LCP, INP и CLSjunior
- CLS: почему происходят сдвиги лейаута и как их остановитьmiddle
- Трейдоффы метрик, RUM-атрибуция и цикл CI+полеsenior
- Общая картина: от URL до LCP до INP как эстафетаjunior
- Восемь слоёв трассировки: от service worker до второй навигацииmiddle
- Пять канонических поломок: где производство стабильно ломаетсяsenior
- Метод трёх треков: чтение трасс и построение системы мониторингаsenior
- Что такое cache stampede и почему он делает всё хужеjunior
- Лок и single-flight: ограничение параллельных rebuildmiddle
- XFetch: вероятностное раннее истечение без координацииmiddle
- Stale-while-revalidate и CDN request coalescingmiddle
- Детектирование stampede и дизайн TTL для продакшенаmiddle
- Метастабильный сбой, fencing-токены и production-постмортемыsenior
- Что такое отношение: таблицы, строки, ключи и ограниченияjunior
- Ограничения, ключи и типы данных Postgresmiddle
- Нормальные формы, денормализация и почему схемы «прилипают»middle
- JSONB, массивы и когда side table побеждаетmiddle
- Heap-хранилище, TOAST и выравнивание колонокsenior
- Целостность схемы: deferral, версионирование и сбои в продакшнеsenior
- Реляционная модель vs документные, wide-column, граф и key-valuesenior
- Index-only scan, Visibility Map и INCLUDEsenior
- Типичные сбои в продакшне и аудит индексовsenior
- pg_statistic, ANALYZE и производственная наблюдаемостьmiddle
- Производственные режимы отказа и стабильность плановsenior
- MVCC: как Postgres раздаёт согласованные снимкиjunior
- Заголовок tuple и механика снимковmiddle
- HOT-обновления и уровни изоляцииmiddle
- VACUUM, bloat и autovacuummiddle
- CLOG, XID wraparound и MultiXactsenior
- SSI и production-тюнинг autovacuumsenior
- Реальные провалы MVCC, deployment-паттерны и распределённые снимкиsenior
- Connection pool: зачем амортизировать стоимость backend Postgresjunior
- Режимы PgBouncer: session, transaction и statementmiddle
- Размер пула: формула (ядра × 2) + шпинделей и двухуровневый стекmiddle
- Исчерпание пула и idle-in-transaction: сценарий отказа в 3 ночиmiddle
- Миграция на transaction mode: план развёртывания и prepared statements в PgBouncer 1.21middle
- Процессная модель Postgres и почему увеличение max_connections снижает производительностьsenior
- Ландшафт пулеров 2026, serverless connection storms и полная таксономия отказовsenior
- Что такое миграция схемы и почему она заменяет ad-hoc DDLjunior
- ADD COLUMN: мгновенно в PG 11+ против перезаписи в старом Postgresjunior
- Режим отказа очереди блокировок: почему мгновенный DDL может заморозить базуmiddle
- Безопасные DDL-паттерны: NOT VALID, CONCURRENTLY и исправления небезопасных операцийmiddle
- Expand-contract: нулевой простой для ломающих изменений схемыmiddle
- Advisory-блокировки, инструменты миграций и координация деплояsenior
- Таксономия сбоев миграций и дисциплина продакшнаsenior
- Зачем нужно шардирование: потолок одного Postgresjunior
- Выбор ключа шарда: стратегии hash, range, list и directorymiddle
- Партиционирование против шардирования: одно слово, два разных понятияmiddle
- Ко-локация и Citus: инвариант, делающий шардирование пригодным к использованиюmiddle
- Режим отказа hot shard: обнаружение, изоляция и долгосрочная политикаmiddle
- Schema-based шардирование и альтернативы мультиарендностиsenior
- Онлайн-решардинг, 2PC и операционная стоимость шардированияsenior
- Семь актов: от CREATE TABLE до Citusjunior
- Акты 1–3 в глубину: схема, индексы и статистика планировщикаmiddle
- Акты 4–6 в глубину: MVCC bloat, connection pooling и безопасные миграцииmiddle
- Акт 7 в глубину: шардинг, co-location и семиуровневый каскад трейдоффовmiddle
- Наблюдаемость, антипаттерны и производственный триажsenior
- Роли Raft, term и почему majority-кворум предотвращает split brainjunior
- Как Raft реплицирует log entry и решает, что его безопасно коммититьmiddle
- Выборы лидера в Raft: таймауты, правила голосования и четыре свойства безопасностиmiddle
- Raft в реальном мире: partition, медленный диск и клиентская маршрутизацияmiddle
- Расширения Raft: pre-vote, learner, snapshot и линеаризуемые чтенияsenior
- Raft в production: membership change, Multi-Raft и observabilitysenior
- Где происходит data fetching — и почему это решает LCPjunior
- Fetch waterfall''''ы — диагностика и лечение через Promise.allmiddle
- React Server Components и Suspense streamingmiddle
- Клиентский кэш: TanStack Query, SWR и stale-while-revalidatemiddle
- LCP, prefetch и race conditions в интерактивном fetchingmiddle
- Senior internals: RSC payload, слои кэша и production паденияsenior
- Трёхстороннее рукопожатие TCPjunior
- Номера последовательности и состояние соединенияmiddle
- DNS: что делает и зачем существуетjunior
- Обход резолвера: перенаправления, типы записей и gluemiddle
- TTL, кеширование и распространение DNSmiddle
- Рукопожатие за 1 RTT: key share и ECDHEmiddle
- Возобновление сессии и 0-RTTmiddle
- WebSocket: HTTP-апгрейд до постоянного соединенияjunior
- Формат WebSocket-фрейма: opcodes, маскирование, фрагментацияmiddle
- Backpressure в WebSocket: когда клиенты не успеваютmiddle
- Реконнект: jittered backoff, thundering herd, восстановление сообщенийsenior
- WebSocket в масштабе: HTTP/2 мультиплексирование, permessage-deflate, C10Msenior
- WebSocket в production: прокси, безопасность и распределённая архитектураsenior
- Что делают обратные проксиjunior
- Health checks, connection draining и slow startmiddle
- Session affinity, consistent hashing и правильное решениеmiddle
- Retry-бури, circuit breakers и load sheddingsenior
- Устойчивая архитектура LB: anycast, zone-aware маршрутизация и observabilitysenior
- Почему QUIC, а не TCP+TLSjunior
- Connection ID и миграция сетиmiddle
- Возобновление 0-RTT и шифрование пакетовsenior
- DDoS: что это и почему работаетjunior
- Атаки усиления и истощение состоянияmiddle
- Ограничение скорости: алгоритмы и архитектураmiddle
- WAF, межсетевые экраны, mTLS и HSTSmiddle
- Отравление DNS-кэша и BGP-перехватsenior
- Эшелонированная защита и экономика атакsenior
- DNS, TCP, TLS по очереди: куда уходят миллисекундыmiddle
- Перехват прокси и шлюзы безопасности: rate limiter, WAF, mTLSmiddle
- Альтернативные пути: QUIC 0-RTT, WebSocket upgrade, миграция соединенияmiddle
- Наблюдаемость: распределённые трейсы, USE/RED и семплированиеsenior
- Устойчивость: каскадные повторы, circuit breakers и error budgetsenior
- Что такое три сигнала: метрики, логи, трейсыjunior
- Зачем нужны структурные логи: дневник против таблицыjunior
- Схема продакшн-лога: поля, которые несёт каждая строкаmiddle
- PII-редакция и log injectionsenior
- OTel Logs Data Model и audit-логи как подсистемаsenior
- SLI, SLO и error budget: надёжность в числахjunior
- Error budget policy, latency SLO и составные journeysmiddle
- Продакшн-отказы SLO, самонаблюдаемость, безопасность и общая картинаsenior
- Петля инцидента: от пейджера до постмортема до предотвращенияmiddle
- Cache lines и false sharing: когда параллелизм замедляет кодmiddle
- SIMD и data layout: AoS vs SoA и разница в 4–8xmiddle
- Cache-oblivious алгоритмы, PGO и production failuressenior
- GC в production: наблюдаемость, безопасность, edge cases и управление флотомsenior
- Batching: амортизируй фиксированную цену каждой операцииjunior
- Окно батчинга: размер и время ожиданияmiddle
- Batching в Kafka и Postgresmiddle
- io_uring и наблюдаемость пакетированияmiddle
- От Nagle до io_uring: эволюция пакетированияmiddle
- Backpressure, изоляция сбоев и безопасность батчей в продакшенеsenior
- CI enforcement и RUM: делаем бюджеты рабочимиmiddle
- V8 JIT-пайплайн, HTTP-приоритеты и безопасность bundlesenior
- Цикл performance: дисциплина, а не проектjunior
- Классификация и исправление: сопоставление family bottleneck с методамиmiddle
- Observability-стек и CI gates: ловить регрессии до выпускаmiddle
- От инцидента к enforcement: SLO burn до верифицированного исправления за 35 минутmiddle
- Культура, экономика и масштаб performancesenior
- At-most-once, at-least-once, exactly-once: три контракта доставкиjunior
- Три ножки сбоя — где реально происходят дубликаты и потериmiddle
- Consumer-side dedup: самый дешёвый путь к exactly-once processingmiddle
- Kafka exactly-once semantics: idempotent producer и транзакцииmiddle
- SQS visibility timeout, DLQ и outbox patternmiddle
- Exactly-once в production: impossibility-доказательство, гибридные паттерны и реальные инцидентыsenior
- Что такое OAuth и почему пароли — не ответjunior
- Authorization code flow с PKCEmiddle
- Валидация ID-токена и управление JWKS-кешемmiddle
- Ротация refresh-токенов и scope-based least privilegemiddle
- Sender-constrained токены: DPoP и mTLSsenior
- OAuth в production: audience атаки, observability и реальные провалыsenior
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.