Функція queueMicrotask()
queueMicrotask(callback) ставить передану функцію в чергу мікротасок, і вона виконається одразу після завершення поточного синхронного коду, але раніше за будь-яку макротаску. Це найпростіший спосіб відкласти дію «на найближчий момент, коли стек спорожніє», не створюючи для цього проміс.
Теорія
TL;DR
queueMicrotask(fn)додаєfnу Microtask Queue і повертаєundefined.- Мікротаска виконується після того, як спорожніє Call Stack, але до першої макротаски (
setTimeout,setInterval, події, I/O). - Поведінка ідентична до
Promise.resolve().then(fn), лише без витрат на створення об'єкта Promise. - Event Loop вичерпує чергу мікротасок повністю, разом із тими, що додалися під час її обробки.
- Нескінченне додавання мікротасок блокує Event Loop: жодна макротаска не дочекається своєї черги.
- Стандарт з ECMAScript 2019, підтримується всіма сучасними браузерами та Node.js.
Швидкий приклад
console.log('A');
queueMicrotask(() => console.log('B'));
console.log('C');
// Output:
// A
// C
// BСпершу виконується весь синхронний код (A, потім C). Далі Event Loop бачить, що стек порожній, і виконує мікротаску (B), перш ніж переходити до макротасок.
Що саме робить queueMicrotask()
Сигнатура гранично проста:
queueMicrotask(callback);Функція робить дві речі:
- додає
callbackу чергу мікротасок; - гарантує, що він виконається після завершення поточної функції, але до будь-якого
setTimeout,setInterval, події чи іншої макротаски.
Під капотом порядок такий:
- Поточний код у Call Stack виконується до кінця.
- Після його завершення Event Loop перевіряє чергу мікротасок.
- Усі мікротаски з черги виконуються по порядку, одна за одною.
- Тільки після цього береться наступна макротаска (наприклад, колбек
setTimeout).
Порядок виконання поруч із таймером
setTimeout(() => console.log('timeout'), 0);
queueMicrotask(() => console.log('microtask'));
console.log('sync');
// Output:
// sync
// microtask
// timeoutПорядок пояснюється трьома рівнями:
sync, це синхронний код, він виконується першим;microtask, це черга мікротасок, вона спустошується одразу після синхронного коду;timeout, це черга макротасок, до неї Event Loop доходить в останню чергу.
Навіть setTimeout(fn, 0) ніколи не випередить мікротаску: нульова затримка означає лише «постав у чергу макротасок якнайшвидше», а не «виконай негайно».
Порівняння з Promise.then
queueMicrotask() працює так само, як Promise.resolve().then(callback), але без створення об'єкта Promise, що робить його трохи швидшим і простішим.
Promise.resolve().then(() => console.log('microtask via Promise'));
queueMicrotask(() => console.log('microtask via queueMicrotask'));Обидва колбеки потраплять у чергу мікротасок і виконаються раніше за будь-які таймери. Різниця лише в ціні: варіант із промісом додатково створює об'єкт Promise і ланцюжок then, тоді як queueMicrotask() просто кладе функцію в чергу. Якщо результат вам не потрібен і ланцюжок не будується, queueMicrotask() виражає намір точніше.
Вкладені мікротаски і ризик заблокувати Event Loop
Усі мікротаски виконуються підряд. Якщо всередині мікротаски створити нову, вона додасться в ту саму чергу і виконається до переходу до макротаски:
queueMicrotask(() => {
console.log('A');
queueMicrotask(() => console.log('B'));
});
// Output:
// A
// BЗвідси випливає небезпека: чергу мікротасок можна зробити нескінченною.
function loop() {
queueMicrotask(loop);
}
loop();Такий код «підвісить» Event Loop: мікротаски виконуватимуться нескінченно, і жодна макротаска (setTimeout, кліки, рендер, мережеві колбеки) не дійде до виконання. Вкладку браузера це фактично заморозить.
Підсумок
| Властивість | Значення |
|---|---|
| Що робить | Додає функцію в чергу мікротасок |
| Коли виконується | Після поточного коду, але до будь-яких макротасок |
| Аналог | Promise.resolve().then(fn) |
| Черга | Microtask Queue |
| Повертає | undefined |
| Використання | Легка і швидка альтернатива промісам для мікротасок |
| Стандарт | ECMAScript 2019, усі сучасні браузери та Node.js |
Типові помилки
- Плутати з
setTimeout(fn, 0). Таймер, це макротаска, він виконається пізніше за всі мікротаски, навіть із нульовою затримкою. - Думати, що мікротаска виконується «негайно». Вона все одно чекає, доки поточна функція повністю відпрацює і стек спорожніє.
- Рекурсивно додавати мікротаски. Це не «розбиття роботи на частини», а гарантоване блокування Event Loop, бо черга мікротасок не віддає керування макротаскам.
- Використовувати для важких обчислень. Мікротаска блокує рендер так само, як синхронний код. Для розбиття важкої роботи потрібні макротаски (
setTimeout) або окремий Worker. - Очікувати повернене значення.
queueMicrotask()завжди повертаєundefined, у ньому немає ланцюжка, як у промісі, і немає способу дізнатися результат колбека. - Покладатися на нього для обробки помилок. Виняток усередині колбека не потрапляє в жоден
catch, а спливає як необроблена помилка, тому обробляти її треба всередині самої функції.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.