← Назад к вопросам
Как отработает очередь из микро и макротасков?
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();
// Все выполнятся в одном микротаске
// Один перерендер вместо трёх!
}
Итоговая таблица
| Тип | Где | Примеры | Приоритет | Когда |
|---|---|---|---|---|
| Sync | Call Stack | console.log(), функции | ВЫСШИЙ | Сразу |
| Micro | Microtask Queue | Promise, async/await, queueMicrotask | СРЕДНИЙ | После синхрона, перед макро |
| Macro | Macrotask Queue | setTimeout, setInterval, I/O | НИЗШИЙ | После всех микротасков |
| rAF | - | requestAnimationFrame | - | Перед рендером, после микро |
Лаконично: Синхронный код → все микротаски → один макротаск → повтор