← Назад к вопросам

Являются ли стеками micro и macro tasks

2.0 Middle🔥 171 комментариев
#Браузер и сетевые технологии

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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 есть:

  1. Microtask Queue — очередь микротасков (FIFO)
  2. Macrotask Queue (Task Queue) — очередь макротасков (FIFO)
  3. 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 кода и отладки сложных проблем синхронизации.