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

Как отработает очередь из микро и макротасков?

2.0 Middle🔥 141 комментариев
#JavaScript Core

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

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

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

Микротаски и макротаски: порядок выполнения в JavaScript

Это одна из самых сложных концепций в JavaScript, но она критична для понимания асинхронного кода, performance и предотвращения ошибок. Разбираю Event Loop детально.

1. Что такое микротаски и макротаски

В JavaScript есть три уровня выполнения кода:

const executionLevels = {
  // 1. Synchronous code (синхронный код)
  synchronous: {
    где: 'Call Stack',
    пример: 'console.log(), переменные, функции',
    выполнение: 'Сразу, блокирует UI'
  },
  
  // 2. Microtasks (микротаски) - ВЫСШИЙ приоритет async
  microtasks: {
    где: 'Microtask Queue',
    примеры: [
      'Promise callbacks (.then, .catch, .finally)',
      'async/await (который это Promise под капотом)',
      'MutationObserver',
      'queueMicrotask()',
      'process.nextTick() (в Node.js)'
    ],
    выполнение: 'После каждого макротаска, перед рендером'
  },
  
  // 3. Macrotasks (макротаски) - НИЗШИЙ приоритет async
  macrotasks: {
    где: 'Macrotask Queue (также называется Task Queue)',
    примеры: [
      'setTimeout / setInterval',
      'setImmediate (Node.js)',
      'requestAnimationFrame (хоть это не совсем макротаск)',
      'I/O операции (читать файл, сетевые запросы)',
      'UI рендеринг',
      'User interaction (клики, скролл)'
    ],
    выполнение: 'После очистки всех микротасков'
  }
};

2. Порядок выполнения Event Loop

Строгий порядок, который браузер следует:

const eventLoopOrder = `
1. Выполнить весь СИНХРОННЫЙ КОД (Call Stack)
   |-- Глобальный код выполняется
   |-- setTimeout, Promise попадают в очереди
   |-- Синхронный код в setTimeout НЕ выполняется!

2. Синхронный код закончился => очистить Call Stack

3. Выполнить ВСЕ МИКРОТАСКИ (Microtask Queue)
   |-- Все Promise .then/.catch
   |-- Все async/await завершения
   |-- MutationObserver
   |-- Выполняются ПОЛНОСТЬЮ, пока очередь не пуста
   |-- ПОТОМ переходим к макротаскам!

4. Браузер может перерендерить (если есть изменения)

5. Выполнить ОДИН МАКРОТАСК (Macrotask Queue)
   |-- Один setTimeout
   |-- Один setInterval
   |-- Одна I/O операция
   |-- Затем вверх на шаг 3 (микротаски снова)

6. Повтор 3-5 пока очереди не пусты
`;

Визуально:

Call Stack пуст?
  |
  YES
  |
  v
Есть микротаски?
  |
  NO  --> Есть макротаски?
  |         |
  YES       YES --> Выполни 1 макротаск
  |         |        |
  |         |        v
  v         |     (вверх на микротаски)
 Выполни    |
  ВСЕ       NO --> Готово
микротаски
  |
  v
(вниз на макротаски)

3. Практический пример: порядок вывода

// Самый классический вопрос интервью
console.log('1. Синхронный код - старт');

setTimeout(() => {
  console.log('2. setTimeout макротаск');
}, 0);

Promise.resolve()
  .then(() => {
    console.log('3. Promise микротаск');
  })
  .then(() => {
    console.log('4. Второй Promise микротаск');
  });

console.log('5. Синхронный код - конец');

/* ВЫВОД:
1. Синхронный код - старт
5. Синхронный код - конец
3. Promise микротаск
4. Второй Promise микротаск
2. setTimeout макротаск

ПОЧЕМУ:
1. Сначала выполняется весь синхронный код (1, 5)
2. Синхронный код завершён
3. Выполняются все микротаски (3, 4) - Promises
4. Выполняется один макротаск (2) - setTimeout
*/

4. Сложный пример с async/await

console.log('Start');

async function test() {
  console.log('Inside async - before await');
  
  // await паузирует здесь и становится микротаском
  await Promise.resolve();
  
  console.log('Inside async - after await');
}

test();

setTimeout(() => {
  console.log('setTimeout');
}, 0);

console.log('End');

/* ВЫВОД:
Start
Inside async - before await
End
Inside async - after await
setTimeout

ПОЧЕМУ:
1. Синхронный код: Start, Inside async - before await, End
2. После await в async - это микротаск
3. setTimeout - макротаск (выполняется ПОСЛЕДНИМ)
*/

5. Пример с DOM изменениями

console.log('1. Start');

const button = document.querySelector('button');

// Изменяем DOM
button.textContent = 'Клик!';

Promise.resolve().then(() => {
  console.log('2. Promise микротаск');
  // Меняем DOM в микротаске
  button.textContent = 'После Promise';
});

setTimeout(() => {
  console.log('3. setTimeout макротаск');
  // Меняем DOM в макротаске
  button.textContent = 'После setTimeout';
}, 0);

console.log('4. End');

/* ВЫВОД (консоль):
1. Start
4. End
2. Promise микротаск
3. setTimeout макротаск

ВИДИМО НА СТРАНИЦЕ:
1. "Клик!" - сразу после синхронного кода
2. "После Promise" - выполнился Promise (микротаск)
3. Затем браузер перерендерит
4. "После setTimeout" - выполнился setTimeout (макротаск)

ВАЖНО: DOM обновляется ОДИН РАЗ после всех микротасков,
потом ещё раз после каждого макротаска.
*/

6. queueMicrotask() - явное добавление микротаска

console.log('Start');

queueMicrotask(() => {
  console.log('Явный микротаск через queueMicrotask');
});

setTimeout(() => {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(() => {
  console.log('Promise микротаск');
});

console.log('End');

/* ВЫВОД:
Start
End
Явный микротаск через queueMicrotask
Promise микротаск
setTimeout

ВСЕ МИКРОТАСКИ выполняются вместе, независимо от порядка!
*/

7. Опасный пример: бесконечный цикл микротасков

// ❌ ОПАСНО: это создаст бесконечный цикл!
function recursiveMicrotask() {
  queueMicrotask(() => {
    console.log('Микротаск');
    recursiveMicrotask(); // Добавляем новый микротаск
  });
}

recursiveMicrotask();

setTimeout(() => {
  console.log('setTimeout НИКОГДА не выполнится!');
  // Потому что микротаски выполняются бесконечно
  // setTimeout никогда не получит очередь
}, 0);

// Браузер зависнет!

8. Performance и практическое применение

// ❌ Плохо: много макротасков
function slowBatch(items) {
  items.forEach(item => {
    // Каждый setTimeout = отдельный макротаск
    // Браузер может перерендерить между ними
    // UI будет тормозить
    setTimeout(() => processItem(item), 0);
  });
}

// ✅ Хорошо: группируем в микротаски
function fastBatch(items) {
  items.forEach(item => {
    // Все выполнятся ДО рендера
    queueMicrotask(() => processItem(item));
  });
}

// ✅ Альтернатива: используем Promise (тоже микротаск)
function fastBatchWithPromise(items) {
  items.forEach(item => {
    Promise.resolve().then(() => processItem(item));
  });
}

// Реальный пример: обработка большого списка
function processList(items) {
  const batch = [];
  
  items.forEach((item, index) => {
    batch.push(item);
    
    // Обрабатываем пакет каждые 100 элементов
    // Но в микротаске, не макротаске
    if (batch.length === 100 || index === items.length - 1) {
      queueMicrotask(() => {
        processBatch(batch);
        batch.length = 0;
      });
    }
  });
}

9. requestAnimationFrame (особый случай)

// requestAnimationFrame - это НЕ совсем макротаск
// Выполняется ПЕРЕД рендером, но ПОСЛЕ микротасков

console.log('1. Start');

setTimeout(() => {
  console.log('2. setTimeout');
}, 0);

Promise.resolve().then(() => {
  console.log('3. Promise');
});

requestAnimationFrame(() => {
  console.log('4. requestAnimationFrame');
});

console.log('5. End');

/* ВЫВОД:
1. Start
5. End
3. Promise (микротаск)
4. requestAnimationFrame (перед рендером)
2. setTimeout (макротаск)
*/

10. Практический совет: как использовать правильно

// Нужна операция как можно скорее? -> Используй микротаск
function criticalUpdate(data) {
  queueMicrotask(() => {
    updateState(data);
  });
}

// Нужно дать браузеру перерисовать? -> setTimeout(fn, 0)
function deferUntilNextFrame() {
  setTimeout(() => {
    // Это выполнится ПОСЛЕ рендера
  }, 0);
}

// Нужен точный timing для анимации? -> requestAnimationFrame
function smoothAnimation() {
  requestAnimationFrame(() => {
    // Выполнится перед следующим frame (60fps)
  });
}

// Real-world: оптимизация React setState
function handleClick() {
  // React батчит обновления в микротаски
  setState1();
  setState2();
  setState3();
  
  // Все выполнятся в одном микротаске
  // Один перерендер вместо трёх!
}

Итоговая таблица

ТипГдеПримерыПриоритетКогда
SyncCall Stackconsole.log(), функцииВЫСШИЙСразу
MicroMicrotask QueuePromise, async/await, queueMicrotaskСРЕДНИЙПосле синхрона, перед макро
MacroMacrotask QueuesetTimeout, setInterval, I/OНИЗШИЙПосле всех микротасков
rAF-requestAnimationFrame-Перед рендером, после микро

Лаконично: Синхронный код → все микротаски → один макротаск → повтор

Как отработает очередь из микро и макротасков? | PrepBro