Microtasks vs Macrotasks
Microtask queue (Promise.then, queueMicrotask, MutationObserver) дренируется полностью после каждого task и перед рендером; незнание этого порядка — источник самых каверзных вопросов на Senior-собесах.
Event Loop — одна итерация ┌─────────────────────────────────────────────────────┐ │ 1. Взять ОДИН Task из Macrotask Queue │ │ (setTimeout / setInterval / I/O / UI event) │ │ │ │ 2. Выполнить Task (Call Stack опустошается) │ │ │ │ 3. Дренировать Microtask Queue ДО ПУСТОТЫ │ │ ┌────────────────────────────────────┐ │ │ │ Promise .then / .catch / .finally │ │ │ │ queueMicrotask(fn) │ │ │ │ MutationObserver callbacks │ │ │ │ (новые microtasks → в эту же очередь)│ │ │ └────────────────────────────────────┘ │ │ │ │ 4. Render (браузер, если нужна перерисовка) │ │ rAF callbacks → style → layout → paint │ │ │ │ 5. Перейти к шагу 1 │ └─────────────────────────────────────────────────────┘ Macrotask queue Microtask queue [T1][T2][T3] … [µ1][µ2][µ3] … ↑ ↑ один за раз все до конца, включая добавленные в процессе
// Классический вопрос на собесе — предсказать порядокconsole.log('1');
setTimeout(() => console.log('2'), 0); // macrotask
Promise.resolve().then(() => console.log('3')); // microtaskqueueMicrotask(() => console.log('4')); // microtask
Promise.resolve() .then(() => { console.log('5'); return Promise.resolve(); // +1 microtask-тик (resolve с promise) }) .then(() => console.log('6'));
console.log('7');
// Порядок: 1 → 7 → 3 → 4 → 5 → 6 → 2// Объяснение: sync (1,7) → все microtasks (3,4,5,6) → macrotask (2)// Опасность: бесконечные microtasks = hang (I/O starved)function infiniteMicro() { Promise.resolve().then(infiniteMicro); // никогда не выйдет из microtask queue}// infiniteMicro(); // ← заморозит браузер/Node.js!
// Безопасная альтернатива — macrotask даёт слот рендеру и I/Ofunction infiniteMacro() { setTimeout(infiniteMacro, 0); // yield каждую итерацию}
// queueMicrotask для батчинга синхронных измененийlet pending = false;function scheduleFlush() { if (!pending) { pending = true; queueMicrotask(() => { flush(); // один flush на все изменения в текущем task pending = false; }); }}Итог: Microtask queue — высокоприоритетная очередь, которая полностью опустошается после каждого macrotask; это гарантирует выполнение Promise-колбэков до следующего setTimeout и до рендера.