Являются ли стеками micro и macro tasks
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Microtasks и Macrotasks: Стеки ли они?
Этот вопрос касается одного из самых сложных аспектов JavaScript — цикла событий (Event Loop) и очередей задач. Коротко: нет, microtasks и macrotasks не являются стеками, это очереди (queues), но часто ошибочно называются "стеками" из-за их поведения.
Что такое Stack, Queue и Task Queue?
Stack (стек) — структура данных LIFO (Last In, First Out). Последний добавленный элемент будет первым извлечён:
const stack = [];
stack.push(1); // [1]
stack.push(2); // [1, 2]
stack.push(3); // [1, 2, 3]
stack.pop(); // [1, 2] — вытащили 3 (последний добавленный)
Queue (очередь) — структура данных FIFO (First In, First Out). Первый добавленный элемент будет первым извлечён:
const queue = [];
queue.push(1); // [1]
queue.push(2); // [1, 2]
queue.push(3); // [1, 2, 3]
queue.shift(); // [2, 3] — вытащили 1 (первый добавленный)
Microtasks и Macrotasks: Это очереди, не стеки
В JavaScript Event Loop есть:
- Microtask Queue — очередь микротасков (FIFO)
- Macrotask Queue (Task Queue) — очередь макротасков (FIFO)
- Call Stack — стек вызовов для синхронного кода
// Это очереди, не стеки — обрабатываются в порядке FIFO
// Микротаски:
Promise.resolve().then(() => console.log('micro 1'));
Promise.resolve().then(() => console.log('micro 2'));
// Макротаски:
setTimeout(() => console.log('macro 1'), 0);
setTimeout(() => console.log('macro 2'), 0);
console.log('sync');
// Вывод:
// sync
// micro 1 (очередь, в порядке добавления)
// micro 2
// macro 1 (очередь, в порядке добавления)
// macro 2
Event Loop: Как это работает?
Цикл событий обрабатывает задачи в определённом порядке:
1. Выполнить весь synchronous код (Call Stack)
2. Выполнить ВСЕ microtasks из Microtask Queue
3. Выполнить ОДНУ macrotask из Macrotask Queue
4. Выполнить все microtasks (если они добавились в результате macrotask)
5. Повторить с шага 3
Примеры Microtasks
// 1. Promise.then(), .catch(), .finally()
Promise.resolve().then(() => console.log('promise'));
// 2. queueMicrotask()
queueMicrotask(() => console.log('microtask'));
// 3. MutationObserver
const observer = new MutationObserver(() => console.log('mutation'));
observer.observe(document.body, { subtree: true });
document.body.textContent = 'changed'; // Срабатывает microtask
// 4. async/await (это синтаксический сахар для Promise)
async function example() {
await Promise.resolve();
console.log('async await'); // Это microtask
}
Примеры Macrotasks
// 1. setTimeout / setInterval
setTimeout(() => console.log('timeout'), 0);
// 2. setImmediate (в Node.js)
setImmediate(() => console.log('immediate'));
// 3. requestAnimationFrame
requestAnimationFrame(() => console.log('raf'));
// 4. I/O операции
fs.readFile('file.txt', () => console.log('file read'));
// 5. UI рендеринг
Демонстрация: Порядок выполнения
console.log('1: sync start');
setTimeout(() => {
console.log('2: setTimeout 1');
Promise.resolve().then(() => console.log('3: promise inside setTimeout'));
}, 0);
Promise.resolve()
.then(() => {
console.log('4: promise 1');
setTimeout(() => console.log('5: setTimeout inside promise'), 0);
})
.then(() => console.log('6: promise 2'));
queueMicrotask(() => console.log('7: queueMicrotask'));
console.log('8: sync end');
/* Вывод:
1: sync start
8: sync end
4: promise 1 (microtask очередь)
7: queueMicrotask
6: promise 2 (microtask очередь)
2: setTimeout 1 (macrotask)
3: promise inside setTimeout (microtask)
5: setTimeout inside promise (macrotask)
*/
Визуализация Event Loop
┌─────────────────────────────────────────────────┐
│ JavaScript Engine │
│ ┌──────────────────────────────────────────┐ │
│ │ Call Stack (LIFO) — синхронный код │ │
│ │ (функции вызываются и выполняются) │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
↓
Весь синхронный код выполнен?
↓
┌─────────────────────────────────────────────────┐
│ Event Loop │
│ ┌──────────────────────────────────────────┐ │
│ │ Microtask Queue (FIFO) │ │
│ │ Выполнить ВСЕ микротаски │ │
│ └──────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────┐ │
│ │ Macrotask Queue (FIFO) │ │
│ │ Выполнить ОДНУ макротаску │ │
│ └──────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────┐ │
│ │ Rendering (если нужно) │ │
│ └──────────────────────────────────────────┘ │
│ ↓ (цикл повторяется) │
└─────────────────────────────────────────────────┘
Практический пример из реальной жизни
const button = document.querySelector('button');
button.addEventListener('click', () => {
console.log('1: click handler');
Promise.resolve().then(() => console.log('2: promise'));
setTimeout(() => console.log('3: timeout'), 0);
button.textContent = 'Clicked'; // Запланирует рендеринг
});
// Клик по кнопке выведет:
// 1: click handler
// 2: promise (microtask)
// (браузер отрисовывает изменения)
// 3: timeout (macrotask)
Важное уточнение о терминологии
В спецификации ECMAScript используются термины:
- Job Queue — для микротасков (Promises, queueMicrotask)
- Task Queue — для макротасков (setTimeout, I/O)
Термин "стек" иногда неправильно применяется потому, что в некоторых контекстах эти очереди могут казаться стеками, но это ошибка. Они действительно FIFO очереди.
Почему это важно для разработчика?
// Знание порядка выполнения позволяет писать предсказуемый код
let state = 0;
Promise.resolve().then(() => {
state = 1;
console.log('Микротаск, state =', state);
});
setTimeout(() => {
state = 2;
console.log('Макротаск, state =', state);
}, 0);
console.log('Синхронный код, state =', state);
// Вывод:
// Синхронный код, state = 0
// Микротаск, state = 1
// Макротаск, state = 2
// Без понимания Event Loop очень сложно отладить асинхронный код
Заключение
Microtasks и macrotasks — это очереди (queues), а не стеки. Они обрабатываются в порядке FIFO (First In, First Out) и следуют строгому порядку в Event Loop. Понимание этого различия критически важно для написания корректного асинхронного JavaScript кода и отладки сложных проблем синхронизации.