← Назад к вопросам
Как поставить микрозадачу в очередь?
3.0 Senior🔥 111 комментариев
#JavaScript Core#Браузер и сетевые технологии
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Микрозадачи (Microtasks) в JavaScript
Микрозадачи — это асинхронные операции, которые выполняются с более высокий приоритетом, чем макротаски (setTimeout, setInterval). Они выполняются в конце текущего события.
Что такое Event Loop
Чтобы понять микрозадачи, нужно разобраться в Event Loop:
1. Синхронный код
2. Микротаски (Promise, queueMicrotask)
3. Макротаски (setTimeout, setInterval)
4. Рендеринг (если нужно)
5. Повторить
Как поставить микрозадачу в очередь
1. Promise (самый частый способ)
// Promise автоматически создает микрозадачу
Promise.resolve()
.then(() => {
console.log('Микрозадача 1');
})
.then(() => {
console.log('Микрозадача 2');
});
console.log('Синхронный код');
setTimeout(() => {
console.log('Макротаска');
}, 0);
// Результат:
// Синхронный код
// Микрозадача 1
// Микрозадача 2
// Макротаска
2. queueMicrotask (явный способ)
console.log('Старт');
queueMicrotask(() => {
console.log('Микрозадача');
});
console.log('Конец');
// Результат:
// Старт
// Конец
// Микрозадача
3. MutationObserver
// MutationObserver также использует микротаски
const observer = new MutationObserver(() => {
console.log('DOM изменился');
});
observer.observe(document.body, { childList: true });
document.body.appendChild(document.createElement('div'));
Практические примеры
1. Батчинг обновлений состояния
export function useBatchUpdate() {
const [state, setState] = useState(0);
const batchUpdate = () => {
// Все обновления в одной микротаске
queueMicrotask(() => {
setState(prev => prev + 1);
setState(prev => prev + 1);
setState(prev => prev + 1);
// React батчит эти обновления
// Результат: одно переполнение
});
};
return { state, batchUpdate };
}
2. Отложенное выполнение после синхронного кода
function processData(data) {
// Синхронная обработка
const result = expensiveCalculation(data);
// Отложить уведомление до конца текущего события
queueMicrotask(() => {
// Это выполнится ДО setTimeout
notify('Обработка завершена');
});
return result;
}
processData(largeData);
console.log('Не заблокированы!');
3. Реактивные зависимости (Reactive)
class Reactive {
constructor(initialValue) {
this.value = initialValue;
this.subscribers = [];
this.pendingNotifications = false;
}
subscribe(callback) {
this.subscribers.push(callback);
}
setValue(newValue) {
this.value = newValue;
// Собрать все уведомления в микротаску
if (!this.pendingNotifications) {
this.pendingNotifications = true;
queueMicrotask(() => {
this.notifySubscribers();
this.pendingNotifications = false;
});
}
}
notifySubscribers() {
this.subscribers.forEach(cb => cb(this.value));
}
}
const count = new Reactive(0);
count.subscribe(val => console.log('Updated:', val));
count.setValue(1);
count.setValue(2);
count.setValue(3);
setTimeout(() => {
console.log('Макротаска');
}, 0);
// Результат:
// Updated: 3 (все изменения батчены)
// Макротаска
4. Отладка Event Loop
function logEventLoop() {
console.log('1. Синхронный код');
setTimeout(() => {
console.log('2. Макротаска (setTimeout)');
}, 0);
Promise.resolve()
.then(() => {
console.log('3. Микрозадача 1 (Promise)');
queueMicrotask(() => {
console.log('4. Микрозадача 2 (queueMicrotask)');
});
})
.then(() => {
console.log('5. Микрозадача 3 (Promise.then)');
});
queueMicrotask(() => {
console.log('6. Микрозадача 4 (queueMicrotask)');
});
console.log('7. Конец синхронного кода');
}
logEventLoop();
// Результат:
// 1. Синхронный код
// 7. Конец синхронного кода
// 3. Микрозадача 1 (Promise)
// 6. Микрозадача 4 (queueMicrotask)
// 4. Микрозадача 2 (queueMicrotask)
// 5. Микрозадача 3 (Promise.then)
// 2. Макротаска (setTimeout)
5. Порядок выполнения с async/await
async function example() {
console.log('1. Синхронный кар внутри async');
await Promise.resolve();
console.log('2. После await (микрозадача)');
}
console.log('3. До вызова async');
example();
console.log('4. После вызова async');
setTimeout(() => {
console.log('5. Макротаска');
}, 0);
// Результат:
// 3. До вызова async
// 1. Синхронный код внутри async
// 4. После вызова async
// 2. После await (микрозадача)
// 5. Макротаска
6. Получение актуального значения из DOM
function updateDOM(element, data) {
element.textContent = data;
// Макротаска для следующей отрисовки
setTimeout(() => {
console.log('Браузер отрисовал изменения');
}, 0);
// Микрозадача перед отрисовкой
queueMicrotask(() => {
// В этот момент элемент НЕ отрисован, но текст изменился
console.log('textContent:', element.textContent); // data
console.log('offsetHeight:', element.offsetHeight); // старое значение
});
}
Таблица: Макротаски vs Микротаски
| Характеристика | Макротаски | Микротаски |
|---|---|---|
| Примеры | setTimeout, setInterval | Promise, queueMicrotask |
| Приоритет | Ниже | Выше |
| Event Loop | После микротасок | После синхронного кода |
| Рендеринг | После каждой макротаски | Нет гарантий |
| Использование | Отложить выполнение | Батчинг, реактивность |
| Цикл обновления | Раз в макротаске | Несколько раз в макротаске |
Визуализация Event Loop
const visualizeEventLoop = () => {
console.log('%c=== НАЧАЛО СОБЫТИЯ ===', 'color: blue');
console.log('Синхронный код');
// Макротаска
setTimeout(() => {
console.log('%c=== НОВАЯ МАКРОТАСКА ===', 'color: red');
}, 0);
// Микротаска
Promise.resolve().then(() => {
console.log('%c=== МИКРОТАСКА ===', 'color: green');
});
console.log('Еще синхронный код');
console.log('%c=== КОНЕЦ СОБЫТИЯ ===', 'color: blue');
};
visualizeEventLoop();
// Порядок вывода:
// === НАЧАЛО СОБЫТИЯ ===
// Синхронный код
// Еще синхронный код
// === КОНЕЦ СОБЫТИЯ ===
// === МИКРОТАСКА ===
// === НОВАЯ МАКРОТАСКА ===
Лучшие практики
-
Используй queueMicrotask для небольших задач
queueMicrotask(() => { // Выполнится сразу после текущего события }); -
Батчируй обновления состояния
queueMicrotask(() => { setState(1); setState(2); setState(3); // Один переполнение вместо трех }); -
Используй Promise для асинхронного кода
async function fetchData() { const data = await fetch('/api/data'); // Выполнится в микротаске } -
Осторожно с вложенными микротасками
queueMicrotask(() => { queueMicrotask(() => { // Это может привести к бесконечному циклу }); }); -
Помни о рендеринге после макротасок
// Микротаски НЕ вызывают рендеринг queueMicrotask(() => { element.style.color = 'red'; // Не видно до следующей макротаски });
Выводы
- Микрозадачи выполняются ДО макротасок
- queueMicrotask создает микрозадачу явно
- Promise автоматически создает микрозадачу
- Используй для батчинга и реактивности
- Понимание Event Loop критично для оптимизации