Суть Читай реальные JS-сниппеты и предсказывай точный порядок выполнения — синхронный код, microtask, task, rAF и порядок фаз в Node.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Предсказание точного порядка лога — чистейшая проверка, действительно ли ты держишь модель loop. Читай каждый сниппет, отслеживай, в какую очередь попадает каждый колбэк, и выбери вывод, который предскажет внимательный инженер.
Цель
Потренируй трассировку, которую мысленно прогоняешь в каждом async-баге: классифицируй каждый колбэк как sync, microtask, task или работу кадра и упорядочи по фиксированным правилам loop — включая Node-специфичное переупорядочивание.
Heads-up setTimeout и Promise.then откладывают свои колбэки. Синхронно выполняются только A и D; B и C — после текущего task, причём microtask C раньше task B.
Heads-up Microtask checkpoint дренируется раньше, чем loop вообще возьмёт task setTimeout. C (microtask) всегда раньше B (task), независимо от задержки 0 мс.
Heads-up Microtask никогда не прерывают синхронный код. C ставится в очередь при резолве, но выполняется только на checkpoint, после завершения всего синхронного кода (A и D).
Сниппет 2 — async/await без сахара
async function run() { console.log('1'); await null; // suspends; resume is queued as a microtask console.log('2');}console.log('3');run();console.log('4');Promise.resolve().then(() => console.log('5'));
Викторина
Completed
Каков порядок вывода?
Heads-up await никогда не блокирует поток. Он приостанавливает run() и планирует возобновление как microtask; синхронный код после run() (печать 4) выполняется до '2'.
Heads-up Код до первого await выполняется синхронно при вызове run(), поэтому '1' печатается до '4'.
Heads-up Microtask-очередь — это FIFO. Возобновление после await ('2') поставлено раньше .then ('5'), поэтому '2' дренируется первым.
Внутри этого I/O-колбэка каков детерминированный порядок вывода в Node?
Heads-up Внутри I/O-колбэка check-фаза (setImmediate) выполняется в текущей итерации, до timers-фазы следующей итерации (setTimeout). Поэтому immediate здесь предшествует timeout.
Heads-up Всё наоборот: у process.nextTick собственная очередь, дренируемая до microtask-очереди, поэтому nextTick печатается до promise.
Heads-up Порядок исходника нерелевантен. Сначала между фазами дренируются nextTick и microtask; затем порядок фаз (check раньше timers следующей итерации) решает immediate против timeout.
Итог
Любая головоломка с порядком async решается одной трассировкой: сначала весь синхронный код, затем microtask checkpoint дренируется до пустоты (возобновления await и колбэки .then в порядке FIFO), затем шаг рендеринга выполняет rAF на границе кадра, затем loop берёт следующий task (setTimeout, MessageChannel). Node наслаивает сверху машину фаз: process.nextTick дренируется до microtask между каждой фазой, а внутри I/O-колбэка setImmediate (check-фаза, эта итерация) обходит setTimeout(0) (timers-фаза, следующая итерация). Классифицируй каждый колбэк по очереди, затем упорядочивай по этим фиксированным правилам — никогда по позиции в исходнике.