Черга задач (task queue)
Черга задач (Task Queue) це структура даних, у якій зберігаються задачі (колбеки), що очікують на виконання після звільнення стека викликів (Call Stack). Вона є ключовим елементом механізму Event Loop (циклу подій) у JavaScript.
Теорія
TL;DR
- Черга задач зберігає колбеки, які вже готові виконатися, але чекають, доки звільниться стек викликів.
- JavaScript однопотоковий, тому одночасно виконується лише одна задача.
- Event Loop бере першу задачу з черги (FIFO) і кладе її у стек, щойно стек стає порожнім.
- Черг насправді дві: черга макрозадач (
setTimeout, події DOM) і черга мікрозадач (Promise.then,queueMicrotask). - Мікрозадачі завжди виконуються раніше за наступну макрозадачу.
- Порядок такий: стек, потім мікрозадачі, потім макрозадачі.
Швидкий приклад
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
console.log('3');Вивід у консолі:
1
3
2Ключова ідея
JavaScript це однопотокова мова, вона може виконувати лише одну задачу за раз. Коли рушій зайнятий виконанням функцій у стеку викликів, решта задач, наприклад обробники кліків, setTimeout, мережеві колбеки і таке інше, чекають своєї черги у черзі задач (task queue).
Коли стек викликів стає порожнім, Event Loop бере першу задачу з черги і поміщає її у стек. І так по колу, нескінченно.
Покроковий розбір прикладу
console.log('1')виконується одразу і друкує1.setTimeout(...): браузер ставить таймер. Після спрацювання таймера колбек потрапляє у чергу задач, а не виконується миттєво.console.log('3')виконується і друкує3.- Коли стек викликів порожній, Event Loop бере задачу з черги (колбек із
setTimeout) і поміщає її у стек, що друкує2.
Саме тому нульова затримка в setTimeout означає «якнайшвидше після звільнення стека», а не «негайно».
Черга задач проти черги мікрозадач
У JavaScript є дві черги:
| Тип черги | Джерело задач | Коли виконуються |
|---|---|---|
| Task Queue (macro tasks) | setTimeout, setInterval, події DOM, колбеки введення/виведення і таке інше | Після очищення стека і всіх мікрозадач |
| Microtask Queue | Promise.then, queueMicrotask, MutationObserver | Одразу після поточної задачі, до наступної макрозадачі |
Різниця принципова: черга мікрозадач вичерпується повністю, а з черги макрозадач за один оберт циклу береться лише одна задача.
Приклад з мікрозадачами
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');Порядок виводу:
A
D
C
BПояснення:
A,Dвиконуються одразу, це синхронний код у стеку викликів.Cце мікрозадача, вона виконується одразу після очищення стека.Bце макрозадача, вона виконується після всіх мікрозадач.
Коротко про терміни
| Термін | Опис |
|---|---|
| Task Queue | Черга макрозадач, зберігає колбеки від setTimeout, подій і таке інше |
| Microtask Queue | Черга мікрозадач, для Promise.then, queueMicrotask |
| Event Loop | Механізм, що стежить за стеком і чергами та вирішує, що виконувати далі |
| Порядок | Спочатку стек, потім мікрозадачі, потім макрозадачі |
Типові помилки
- Вважати, що
setTimeout(fn, 0)виконаєfnнегайно. Колбек лише стає в чергу задач і чекає, доки стек спорожніє. - Плутати чергу задач зі стеком викликів. Стек це LIFO і те, що виконується зараз; черга це FIFO і те, що виконуватиметься потім.
- Ставити промиси і таймери в один ряд.
Promise.thenіде в чергу мікрозадач і завжди випередить будь-якийsetTimeout, навіть з нульовою затримкою. - Нескінченно породжувати мікрозадачі. Якщо мікрозадача щоразу додає нову мікрозадачу, черга ніколи не спорожніє і макрозадачі та рендеринг просто не отримають керування.
- Думати, що черга робить код паралельним. Усе одно виконання проходить через один потік і один стек, просто в іншому порядку.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.