Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як додати задачу до черги мікрозадач за допомогою queueMicrotask». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`queueMicrotask`** ставить колбек у чергу мікрозадач (microtask queue), яка виконується після спустошення поточного стека викликів, але до колбеків `setTimeout` або `setInterval`. ```javascript console.log("1"); queueMicrotask(() => console.log("2")); // мікрозадача setTimeout(() => console.log("3"), 0); // макрозадача console.log("4"); // Виведення: 1, 4, 2, 3 ``` **Головне:** черга мікрозадач повністю спустошується до будь-якої макрозадачі, навіть `setTimeout(0)`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`queueMicrotask(callback)`** планує виконання функції в черзі мікрозадач (microtask queue). Цикл подій обробляє цю чергу після того, як поточний стек викликів спустошується, але до наступних макрозадач на кшталт колбеків `setTimeout`. ## Теорія ### TL;DR - Уяви цикл подій як касира в кафе: макрозадачі - це повні замовлення в черзі, мікрозадачі - це швидке "поповнення серветок", яке робиться відразу після поточного клієнта, до початку наступного замовлення - `queueMicrotask` виконується до `setTimeout(0)`, в тій самій точці перевірки мікрозадач що і `Promise.then` - Обидва спрацьовують у порядку FIFO: `queueMicrotask`, поставлений у чергу до `Promise.then`, виконається першим - Використовуй для відкладеної роботи, яка не повинна поступатися черзі макрозадач - Не підходить для важких обчислень: довгий ланцюг мікрозадач блокує відмалювання ### Короткий приклад ```javascript 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](/questions/web-workers) ### Як цикл подій обробляє мікрозадачі У V8 (Chrome і Node.js) `queueMicrotask` додає колбек у `MicrotaskQueue` - зв'язний список внутрішніх об'єктів `Microtask`, прив'язаний до поточного JavaScript global scope. Після завершення поточної макрозадачі [цикл подій](/questions/event-loop) входить у фазу `RunMicrotasks`. Він обходить чергу до повного спустошення, включаючи нові мікрозадачі додані під час самого обходу. Лише тоді відбувається відмалювання або запускається наступна макрозадача. Браузери реалізують це через "microtask checkpoint" із специфікації HTML. Node.js інтегрується з libuv, але дотримується того ж пріоритету. Одна відмінність: у Node.js `process.nextTick` спустошується до `queueMicrotask` і `Promise.then`, тобто має фактично вищий пріоритет, хоча деяка документація теж називає його "мікрозадачею". ### Типові помилки **Очікувати що `setTimeout` спрацює першим:** ```javascript setTimeout(() => console.log("Timeout"), 0); queueMicrotask(() => console.log("Micro")); // виконається першим // Виведення: Micro, Timeout ``` Мікрозадачі спустошуються до будь-якої макрозадачі. Якщо потрібно щоб `setTimeout` спрацював першим, не ставь їх у такому порядку. **Рекурсивний `queueMicrotask` без умови завершення:** ```javascript function recurse() { queueMicrotask(recurse); // немає базового випадку } queueMicrotask(recurse); // Цикл подій ніколи не виходить з фази мікрозадач. Відмалювання зависає. ``` Черга спустошується до порожнього стану. Якщо нові мікрозадачі постійно додаються, вихід ніколи не відбудеться. V8 не зупиняє це автоматично. Додай лічильник або переходь на `setTimeout` для поступальної роботи. **Кидати виняток всередині і очікувати зовнішній try/catch:** ```javascript try { queueMicrotask(() => { throw new Error("Boom"); }); } catch (e) { console.log("Caught"); // не спрацює } // Помилка виникає асинхронно, після поточного стека ``` Обгортай `try/catch` всередині колбека, а не навколо `queueMicrotask`. **Плутати пріоритети Node.js і браузера:** ```javascript 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`, яка обходить і виконує кожен запис, включаючи нові додані під час обходу, до повного спустошення списку. ## Приклади ### Базовий: порядок виконання ```javascript 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 // timeout ``` `queueMicrotask` спрацьовує до `Promise.then`, бо був поставлений у чергу першим. Обидва живуть в одній черзі мікрозадач і обробляються по порядку. ### Середній рівень: групування записів у DOM ```javascript 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 у мікрозадачі. Браузер бачить одну зміну і виконує один розрахунок макету. ### Просунутий рівень: пастка нескінченних мікрозадач ```javascript const loopMicrotask = () => { console.log("ще працює..."); queueMicrotask(loopMicrotask); // планує себе знову }; queueMicrotask(loopMicrotask); // "ще працює..." виводиться нескінченно // setTimeout-колбеки ніколи не спрацьовують // відмалювання браузером ніколи не відбувається ``` Саме такий патерн я бачив під час code review, де хтось намагався опитувати умову через `queueMicrotask` замість `setInterval`. Цикл подій повністю спустошує чергу мікрозадач перед тим як рухатися далі. Якщо кожна мікрозадача додає нову, він ніколи не рухається. Додай лічильник як запобіжник або використовуй `setTimeout` для поступальних перевірок.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.