Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Мікрозадача проміса». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Мікрозадача (microtask) це невелика задача, яка виконується після поточного стека викликів, але до того, як рушій візьме наступну макрозадачу (`setTimeout`, `setInterval`, події DOM). Коли проміс переходить у стан `fulfilled` або `rejected`, його обробники `.then()`, `.catch()`, `.finally()` не виконуються одразу, а стають у чергу мікрозадач. Event Loop спустошує цю чергу повністю, щойно стек викликів порожній, і лише потім переходить до наступної макрозадачі.** ```javascript console.log('A'); setTimeout(() => console.log('B'), 0); Promise.resolve().then(() => console.log('C')); console.log('D'); // A, D, C, B ``` **Ключове:** проміси завжди працюють через чергу мікрозадач, тому їхні колбеки гарантовано виконуються раніше за будь-який таймер.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Мікрозадача (microtask) це невелика задача, яка виконується після поточного стека викликів, але до того, як рушій перейде до макрозадач (`setTimeout`, `setInterval`, події DOM).** Обробники проміса завжди потрапляють саме в чергу мікрозадач, тому й виконуються раніше за таймери. ## Теорія ### TL;DR - Мікрозадачі це «черга термінових задач», яка спустошується одразу після поточного синхронного коду. - `.then()`, `.catch()`, `.finally()` не викликаються синхронно: вони ставляться в чергу мікрозадач. - Після кожної макрозадачі рушій виконує **всі** мікрозадачі, і лише потім бере наступну макрозадачу. - Мікрозадачі виконуються в порядку додавання, а нова мікрозадача, створена під час обробки черги, виконається в тому ж проході. - Тому колбек проміса завжди випереджає `setTimeout(..., 0)`. ### Швидкий приклад ```javascript console.log('1'); Promise.resolve().then(() => console.log('2')); console.log('3'); ``` Результат: ```javascript 1 3 2 ``` Чому саме так: 1. Спершу виконується весь **синхронний код** (у стеку викликів). 2. Потім **Event Loop** бачить, що стек порожній, і виконує **всі мікрозадачі**, у цьому випадку колбек `.then()`. ### Проміси створюють мікрозадачі Коли проміс **переходить у стан `fulfilled` або `rejected`**, його обробники `.then()`, `.catch()`, `.finally()` **не виконуються одразу**, а ставляться в **чергу мікрозадач (Microtask Queue)**. Мікрозадачі створюються: - при `.then()`, `.catch()`, `.finally()` у `Promise`; - при `queueMicrotask(fn)`; - при `MutationObserver` (у браузері); - при `process.nextTick()` у Node.js. Кожен `await` усередині `async`-функції теж планує мікрозадачу: продовження функції після `await` це та сама мікрозадача проміса. ### Черга мікрозадач і черга макрозадач Рушій JavaScript обробляє події так: 1. Виконує весь **синхронний код** (Call Stack). 2. Виконує **всі мікрозадачі**, доки черга не спорожніє. 3. Переходить до **однієї макрозадачі** (наприклад, `setTimeout`). 4. Знову виконує **всі мікрозадачі**, що накопичилися після неї. 5. І так по колу. Порівняння з макрозадачами: ```javascript console.log('A'); setTimeout(() => console.log('B'), 0); Promise.resolve().then(() => console.log('C')); console.log('D'); ``` Вивід: ```javascript A D C B ``` Чому: 1. `A`, `D` синхронно; 2. `C` мікрозадача (від проміса); 3. `B` макрозадача (від `setTimeout`). Мікрозадача `C` виконується **раніше** за таймер `B`. Важливе правило: після виконання **кожної макрозадачі** рушій завжди повністю спустошує чергу **мікрозадач**, перш ніж узяти наступну макрозадачу. Проста аналогія: Event Loop це черга в кафе. Макрозадачі це звичайні великі замовлення, мікрозадачі це швидка решта та розрахунок. Бариста робить замовлення (синхронний код), потім роздає всю решту (мікрозадачі) і лише після цього переходить до наступного клієнта (нової макрозадачі). ### Порядок усередині черги Кілька мікрозадач виконуються строго в порядку додавання: ```javascript Promise.resolve().then(() => console.log('1')); Promise.resolve().then(() => console.log('2')); Promise.resolve().then(() => console.log('3')); console.log('4'); ``` Вивід: ```javascript 4 1 2 3 ``` Усі `.then()` потрапляють в **одну чергу мікрозадач** і виконуються **в порядку додавання**, після завершення синхронного коду (`4`). Мікрозадача, створена всередині іншої мікрозадачі, теж виконається в цьому ж проході: ```javascript Promise.resolve().then(() => { console.log('A'); Promise.resolve().then(() => console.log('B')); }); ``` Вивід: ```javascript A B ``` Після `A` виконується нова мікрозадача `B`, додана під час виконання попередньої. Рушій не виходить із черги, поки вона не спорожніє повністю. ### Підсумок | Тип задачі | Приклади | Коли виконується | | --- | --- | --- | | **Мікрозадачі** | `.then()`, `.catch()`, `.finally()`, `queueMicrotask()` | Після поточного коду, до наступної макрозадачі | | **Макрозадачі** | `setTimeout`, `setInterval`, `fetch`, події DOM | Після всіх мікрозадач поточного тіку | Проміси **завжди використовують мікрозадачі**, тому їхні колбеки виконуються раніше за таймери. ### Типові помилки - **Вважати, що `.then()` виконується синхронно.** Навіть у вже виконаного `Promise.resolve()` колбек піде в чергу і спрацює після решти синхронного коду. - **Розраховувати, що `setTimeout(..., 0)` спрацює «негайно».** Це макрозадача, вона чекає, поки черга мікрозадач спорожніє, тому будь-який `.then()` її випередить. - **Нескінченно підживлювати чергу мікрозадач.** Якщо мікрозадача щоразу додає нову, черга не спорожніє ніколи: рендеринг і таймери просто не отримають шансу виконатися, і сторінка «замерзне». - **Плутати `process.nextTick()` і мікрозадачі промісів у Node.js.** `nextTick` має власну чергу з вищим пріоритетом, вона спустошується раніше за чергу промісів. - **Забувати, що `await` це теж мікрозадача.** Код після `await` не продовжується миттєво, навіть якщо ви чекаєте на вже готове значення.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.