Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Черга макрозадач (macrotask queue)». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Macrotask queue (черга макрозадач) це черга великих асинхронних задач, з якої event loop бере рівно одну задачу за ітерацію.** Туди потрапляють `setTimeout`, `setInterval`, `setImmediate` у Node.js, обробники подій DOM, мережеві колбеки та `MessageChannel`. Після кожної макрозадачі рушій спочатку виконує всі мікрозадачі і тільки потім бере наступну макрозадачу, тому `Promise.then` завжди спрацьовує раніше за `setTimeout(..., 0)`. ```javascript setTimeout(() => console.log('macrotask'), 0); Promise.resolve().then(() => console.log('microtask')); // microtask // macrotask ``` **Ключове:** одна макрозадача за ітерацію event loop, а між ними повністю спустошується черга мікрозадач.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Macrotask queue (черга макрозадач) це черга крупних асинхронних задач, які виконуються по одній за ітерацію Event Loop.** Кожна макрозадача це «велика» подія: спрацював таймер, користувач клікнув, прийшла відповідь від мережі. Черга мікрозадач має вищий пріоритет, тому макрозадачі завжди чекають своєї черги. ## Теорія ### TL;DR - Макрозадача це «велике» асинхронне завдання: `setTimeout`, `setInterval`, `setImmediate` (Node.js), обробники подій DOM, мережеві колбеки, `MessageChannel`. - За одну ітерацію Event Loop виконується рівно **одна** макрозадача. - Після кожної макрозадачі рушій виконує **всі** мікрозадачі, які встигли накопичитися. - Порядок незмінний: макрозадача, усі мікрозадачі, наступна макрозадача, усі мікрозадачі. - Черга макрозадач працює за принципом FIFO: задачі виконуються в порядку додавання. - Пріоритет макрозадач нижчий за мікрозадачі, тому `Promise.then()` спрацює раніше за `setTimeout(..., 0)`. ### Швидкий приклад ```javascript console.log('1'); setTimeout(() => console.log('2 (macrotask)'), 0); Promise.resolve().then(() => console.log('3 (microtask)')); console.log('4'); ``` Результат: ```javascript 1 4 3 (microtask) 2 (macrotask) ``` Чому саме так: 1. `1` і `4` друкуються синхронно, під час виконання основного скрипта. 2. `.then()` додає **мікрозадачу**, вона виконається одразу після того, як стек спорожніє. 3. `setTimeout` додає **макрозадачу**, вона виконається лише на наступній ітерації Event Loop. ### Що потрапляє в чергу макрозадач Джерела макрозадач у браузері та в Node.js: - `setTimeout` - `setInterval` - `setImmediate` (тільки Node.js) - обробники подій DOM: `click`, `load`, `input` і подібні - мережеві колбеки: `XMLHttpRequest`, завершення `fetch` - `MessageChannel` Спільна риса всіх цих джерел: задача приходить «ззовні» рушія JavaScript, від таймера, від вводу-виводу або від користувача. ### Як працює Event Loop крок за кроком Спрощена, але робоча модель циклу: 1. Виконується **весь синхронний код** (Call Stack). 2. Коли стек порожній, виконуються мікрозадачі. 3. Далі рушій бере **одну макрозадачу** з черги і виконує її. 4. Після завершення цієї макрозадачі виконуються **всі** мікрозадачі: колбеки промісів, `queueMicrotask`, `process.nextTick` тощо. 5. Потім береться **наступна макрозадача**, і після неї знову всі мікрозадачі. | Етап | Що робить рушій | | --- | --- | | 1 | Виконує стек синхронного коду | | 2 | Виконує всі мікрозадачі | | 3 | Бере одну макрозадачу з черги | | 4 | Знову виконує всі мікрозадачі | | 5 | Повторює цикл спочатку | Схематично один тік виглядає так: ```javascript // [ Tick 1 ] // Call Stack: synchronous code // v // Microtask Queue: promise callbacks // v // Macrotask Queue: setTimeout, events // v // Event Loop alternates between them ``` > Проста аналогія: Event Loop це ресторан. > Макрозадачі це основні страви, їх подають по одній. > Мікрозадачі це приправи та гарніри, які додають **між** подачами страв. > Після кожної основної страви кухар спершу роздає всі приправи і лише потім береться за наступне замовлення. ### Кілька макрозадач поспіль ```javascript setTimeout(() => console.log('A'), 0); setTimeout(() => console.log('B'), 0); setTimeout(() => console.log('C'), 0); console.log('D'); ``` Результат: ```javascript D A B C ``` Усі три виклики `setTimeout()` потрапляють у чергу макрозадач і виконуються по одній, у порядку додавання (FIFO). Синхронний `console.log('D')` завжди попереду, бо він взагалі не проходить через чергу. ### Мікрозадачі всередині макрозадачі ```javascript setTimeout(() => { console.log('1 (timeout)'); }, 0); setTimeout(() => { console.log('2 (timeout)'); Promise.resolve().then(() => console.log('3 (microtask)')); }, 0); ``` Результат: ```javascript 1 (timeout) 2 (timeout) 3 (microtask) ``` Пояснення: після кожної макрозадачі виконуються всі мікрозадачі, створені під час цієї макрозадачі. Рядок `3` це мікрозадача, породжена другою макрозадачею, тому вона виконається **до того**, як рушій візьме наступну макрозадачу. ### Макрозадачі проти мікрозадач | Характеристика | Macrotask | Microtask | | --- | --- | --- | | Приклади | `setTimeout`, `setInterval`, події DOM, `fetch` | `Promise.then`, `queueMicrotask`, `process.nextTick` | | Коли виконуються | Після того, як спорожніла черга мікрозадач | Після кожної макрозадачі або після синхронного коду | | Кількість за цикл | Одна за ітерацію Event Loop | Усі підряд, доки черга не спорожніє | | Пріоритет | Низький | Високий | | Тип задач | «Зовнішні»: таймери, I/O, події | «Внутрішні»: логіка самого JavaScript | Підсумок в одному рядку на пункт: | Термін | Опис | | --- | --- | | **Macrotask queue** | Черга крупних подій, які виконує браузер або Node.js | | **Що в ній зберігається** | Таймери, I/O, обробники подій, мережеві колбеки | | **Як виконується** | По одній задачі за ітерацію Event Loop | | **Після кожної** | Виконуються всі мікрозадачі | | **Пріоритет** | Нижчий, ніж у мікрозадач: `Promise` спрацює швидше, ніж `setTimeout` | ### Типові помилки - **Думати, що `setTimeout(fn, 0)` виконає `fn` негайно.** Нуль це мінімальна затримка, а не обіцянка. Колбек стає в чергу макрозадач і чекає, доки завершиться синхронний код і спорожніє черга мікрозадач. - **Плутати пріоритети.** `Promise.resolve().then(...)` завжди випередить `setTimeout(..., 0)`, навіть якщо проміс створено пізніше в коді. - **Вважати, що за одну ітерацію виконуються всі макрозадачі.** Ні, лише одна. Решта чекають наступних ітерацій, тому між ними браузер встигає перемалювати сторінку. - **Створювати нескінченний ланцюг мікрозадач.** Якщо мікрозадача породжує нову мікрозадачу, черга ніколи не спорожніє і жодна макрозадача не отримає шансу виконатися: сторінка зависає. - **Розраховувати на точність таймерів.** Довга макрозадача блокує цикл, тому `setTimeout(fn, 100)` може спрацювати значно пізніше за 100 мс. - **Змішувати `setImmediate` і `setTimeout` у Node.js.** Вони належать до різних фаз циклу, і порядок їх виконання на верхньому рівні модуля не гарантований.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.