Як додати задачу до черги мікрозадач за допомогою queueMicrotask
queueMicrotask(callback) планує виконання функції в черзі мікрозадач (microtask queue). Цикл подій обробляє цю чергу після того, як поточний стек викликів спустошується, але до наступних макрозадач на кшталт колбеків setTimeout.
Теорія
TL;DR
- Уяви цикл подій як касира в кафе: макрозадачі - це повні замовлення в черзі, мікрозадачі - це швидке "поповнення серветок", яке робиться відразу після поточного клієнта, до початку наступного замовлення
queueMicrotaskвиконується доsetTimeout(0), в тій самій точці перевірки мікрозадач що іPromise.then- Обидва спрацьовують у порядку FIFO:
queueMicrotask, поставлений у чергу доPromise.then, виконається першим - Використовуй для відкладеної роботи, яка не повинна поступатися черзі макрозадач
- Не підходить для важких обчислень: довгий ланцюг мікрозадач блокує відмалювання
Короткий приклад
console.log("1. Початок скрипту");
setTimeout(() => console.log("4. setTimeout"), 0);
Promise.resolve().then(() => console.log("3. Promise.then"));
queueMicrotask(() => console.log("2. queueMicrotask"));
console.log("5. Кінець скрипту");
// Виведення:
// 1. Початок скрипту
// 5. Кінець скрипту
// 2. queueMicrotask
// 3. Promise.then
// 4. setTimeoutСпочатку виконується синхронний код. Потім повністю спустошується черга мікрозадач у порядку постановки. setTimeout спрацьовує останнім, навіть із затримкою 0.
Ключова різниця від Promise.resolve().then()
І queueMicrotask, і Promise.resolve().then() додають роботу в чергу мікрозадач. Але обгортки відрізняються. Promise.then автоматично розгортає повернуті Promise і направляє помилки через .catch. queueMicrotask - це простий колбек без обробки відхилення: якщо він кидає виняток, помилка з'явиться як необроблений виняток після поточного стека, а не як відхилений Promise. Бери queueMicrotask, коли потрібно просто відкласти виконання після синхронного коду, без зайвого механізму Promise.
Коли використовувати
- Групування записів у DOM: відклади оновлення стилю до завершення всіх синхронних змін, потім зроби один запис
- Синхронізація стану фреймворку: запусти ефект після того, як реактивний стан стабілізується, але до відмалювання браузером
- Очищення після синхронних операцій: виконай teardown-код, не блокуючи чергу макрозадач
- Замість
setTimeout(0)для таймінгу: коли потрібне справжнє "після поточної задачі" без мінімального порогу таймера в 4 мс - Не підходить для важких обчислень і всього, що пов'язане з I/O. Це для макрозадач або Web Workers
Як цикл подій обробляє мікрозадачі
У V8 (Chrome і Node.js) queueMicrotask додає колбек у MicrotaskQueue - зв'язний список внутрішніх об'єктів Microtask, прив'язаний до поточного JavaScript global scope. Після завершення поточної макрозадачі цикл подій входить у фазу RunMicrotasks. Він обходить чергу до повного спустошення, включаючи нові мікрозадачі додані під час самого обходу. Лише тоді відбувається відмалювання або запускається наступна макрозадача.
Браузери реалізують це через "microtask checkpoint" із специфікації HTML. Node.js інтегрується з libuv, але дотримується того ж пріоритету. Одна відмінність: у Node.js process.nextTick спустошується до queueMicrotask і Promise.then, тобто має фактично вищий пріоритет, хоча деяка документація теж називає його "мікрозадачею".
Типові помилки
Очікувати що setTimeout спрацює першим:
setTimeout(() => console.log("Timeout"), 0);
queueMicrotask(() => console.log("Micro")); // виконається першим
// Виведення: Micro, TimeoutМікрозадачі спустошуються до будь-якої макрозадачі. Якщо потрібно щоб setTimeout спрацював першим, не ставь їх у такому порядку.
Рекурсивний queueMicrotask без умови завершення:
function recurse() {
queueMicrotask(recurse); // немає базового випадку
}
queueMicrotask(recurse);
// Цикл подій ніколи не виходить з фази мікрозадач. Відмалювання зависає.Черга спустошується до порожнього стану. Якщо нові мікрозадачі постійно додаються, вихід ніколи не відбудеться. V8 не зупиняє це автоматично. Додай лічильник або переходь на setTimeout для поступальної роботи.
Кидати виняток всередині і очікувати зовнішній try/catch:
try {
queueMicrotask(() => { throw new Error("Boom"); });
} catch (e) {
console.log("Caught"); // не спрацює
}
// Помилка виникає асинхронно, після поточного стекаОбгортай try/catch всередині колбека, а не навколо queueMicrotask.
Плутати пріоритети Node.js і браузера:
process.nextTick(() => console.log("nextTick"));
queueMicrotask(() => console.log("queueMicrotask"));
// Виведення: nextTick, queueMicrotaskУ Node.js process.nextTick має вищий пріоритет ніж queueMicrotask. У браузерах process.nextTick не існує. Для коду який працює в обох середовищах, queueMicrotask відповідає специфікації і є правильним вибором.
Використання в реальних проектах
- Vue 3 -
nextTick()ставить оновлення DOM у чергу черезqueueMicrotaskпісля спустошення реактивності - React 18 - планувальник використовує
queueMicrotaskдля пасивних ефектів (post-commit, до відмалювання), а автоматичне групування тепер поширюється на нативні async-контексти на кшталтfetch - Angular / Zone.js - патчить
queueMicrotaskдля відстеження async-зон і виклику change detection - Preact -
flushSyncвідкладає черезqueueMicrotaskдля групування оновлень - Node.js undici - HTTP-клієнт ставить парсинг відповіді в чергу мікрозадач після завершення I/O
Питання на співбесіді
Q: Який порядок виведення для синхронного коду, queueMicrotask, Promise.then і setTimeout?
A: Спочатку синхронний код. Потім усі мікрозадачі в порядку FIFO - queueMicrotask і Promise.then перебувають в одній черзі, черговість визначається порядком реєстрації. Потім setTimeout.
Q: У чому різниця між queueMicrotask і Promise.resolve().then()?
A: Обидва планують роботу в черзі мікрозадач з однаковим пріоритетом. Promise.then обробляє відхилення і розгортає повернуті Promise. queueMicrotask - простіший колбек без обгортки Promise і без маршрутизації помилок.
Q: Чи блокує queueMicrotask відмалювання?
A: Так. Мікрозадачі спустошуються до відмалювання браузером. Довгий ланцюг або рекурсивний queueMicrotask заморозять інтерфейс. Для роботи що може поступитися циклу відмалювання - requestIdleCallback або setTimeout.
Q: Як process.nextTick співвідноситься з queueMicrotask у Node.js?
A: Обидва схожі на мікрозадачі, але process.nextTick має вищий внутрішній пріоритет і спустошується першим. queueMicrotask відповідає специфікації WHATWG і поводиться ідентично браузеру. Для коду що запускається в обох середовищах - queueMicrotask є більш переносним варіантом.
Q: Як V8 реалізує чергу мікрозадач всередині?
A: V8 використовує MicrotaskQueue - зв'язний список об'єктів v8::internal::Microtask, прив'язаний до поточного JavaScript global scope. Після кожної макрозадачі цикл подій викликає MaybeResolvingMicrotaskQueue, яка обходить і виконує кожен запис, включаючи нові додані під час обходу, до повного спустошення списку.
Приклади
Базовий: порядок виконання
console.log("sync 1");
queueMicrotask(() => console.log("microtask"));
Promise.resolve().then(() => console.log("promise"));
setTimeout(() => console.log("timeout"), 0);
console.log("sync 2");
// sync 1
// sync 2
// microtask <- поставлений у чергу до Promise.then, тому спрацьовує першим
// promise
// timeoutqueueMicrotask спрацьовує до Promise.then, бо був поставлений у чергу першим. Обидва живуть в одній черзі мікрозадач і обробляються по порядку.
Середній рівень: групування записів у DOM
let count = 0;
function increment() {
count += 1;
count += 1; // кілька синхронних змін стану
queueMicrotask(() => {
// один запис у DOM після стабілізації всіх синхронних оновлень
document.getElementById("counter").textContent = count;
console.log("DOM оновлено:", count); // 2
});
}
increment();
// Один розрахунок макету замість двохТакий же патерн використовують nextTick у Vue 3 і планувальник React. Групуй зміни стану синхронно, потім один раз записуй у DOM у мікрозадачі. Браузер бачить одну зміну і виконує один розрахунок макету.
Просунутий рівень: пастка нескінченних мікрозадач
const loopMicrotask = () => {
console.log("ще працює...");
queueMicrotask(loopMicrotask); // планує себе знову
};
queueMicrotask(loopMicrotask);
// "ще працює..." виводиться нескінченно
// setTimeout-колбеки ніколи не спрацьовують
// відмалювання браузером ніколи не відбуваєтьсяСаме такий патерн я бачив під час code review, де хтось намагався опитувати умову через queueMicrotask замість setInterval. Цикл подій повністю спустошує чергу мікрозадач перед тим як рухатися далі. Якщо кожна мікрозадача додає нову, він ніколи не рухається. Додай лічильник як запобіжник або використовуй setTimeout для поступальних перевірок.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.