Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как создать микротаск?
Микротаск (Microtask) — это единица асинхронной работы, которая выполняется после текущего синхронного кода, но перед макротаском (setTimeout, I/O). Микротаски критичны для понимания Event Loop в JavaScript. Все микротаски в очереди выполняются полностью перед тем, как браузер перейдёт к следующему макротаску или рендерингу.
Event Loop: Порядок выполнения
1. Синхронный код
|
v
2. ВСЕ Микротаски (Promise, queueMicrotask, MutationObserver)
|
v
3. Рендеринг (requestAnimationFrame, браузер отрисовывает)
|
v
4. Один Макротаск (setTimeout, setInterval, I/O)
|
v
Обратно к пункту 2
Способы создать микротаск
1. Promise.then() - самый частый способ
Это наиболее используемый способ создания микротаска:
Promise.resolve()
.then(() => {
console.log('Это выполнится в микротаске');
});
// Или с переданным значением
Promise.resolve(42)
.then(value => {
console.log('Значение:', value);
});
// Цепочка микротаков
Promise.resolve()
.then(() => {
console.log('Микротаск 1');
return Promise.resolve();
})
.then(() => {
console.log('Микротаск 2');
});
Все эти микротаски выполнятся ПРЕЖДЕ чем будет выполнен any setTimeout.
2. queueMicrotask() - явный способ
Это встроенная функция для прямого создания микротаска:
queueMicrotask(() => {
console.log('Явный микротаск');
});
// Эквивалентно (но явнее):
Promise.resolve().then(() => {
console.log('Неявный микротаск через Promise');
});
// queueMicrotask изначально был более читаем:
queueMicrotask(() => {
updateUI();
calculateMetrics();
});
3. async/await (синтаксический сахар над Promise)
async function example() {
console.log('Синхронный код');
await Promise.resolve();
console.log('Это выполнится в микротаске');
}
// Эквивалентно:
function example() {
console.log('Синхронный код');
return Promise.resolve()
.then(() => {
console.log('Это выполнится в микротаске');
});
}
4. MutationObserver
Мутация DOM элемента создаёт микротаск:
const observer = new MutationObserver((mutations) => {
console.log('DOM изменилась - микротаск!');
mutations.forEach(mutation => {
console.log('Изменен элемент:', mutation.target);
});
});
// Наблюдаем за изменениями
observer.observe(document.body, {
attributes: true, // Отслеживать изменения атрибутов
attributeOldValue: true,
characterData: true,
subtree: true
});
// Это вызывает микротаск
document.body.setAttribute('data-test', 'value');
5. Promise.prototype.then/catch/finally
fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log('Микротаск 1: данные получены');
})
.catch(error => {
console.log('Микротаск: ошибка обработана');
})
.finally(() => {
console.log('Микротаск: очистка');
});
Практический пример: порядок выполнения
console.log('1. Начало'); // Синхронный код
setTimeout(() => {
console.log('7. Макротаск 1 (setTimeout)');
Promise.resolve().then(() => {
console.log('8. Микротаск в макротаске');
});
}, 0);
Promise.resolve()
.then(() => {
console.log('3. Микротаск 1 (Promise)');
return Promise.resolve();
})
.then(() => {
console.log('4. Микротаск 2 (Promise цепочка)');
});
queueMicrotask(() => {
console.log('5. Микротаск 3 (queueMicrotask)');
});
requestAnimationFrame(() => {
console.log('6. RAF (перед макротаском)');
});
console.log('2. Конец синхронного кода');
// Результат:
// 1. Начало
// 2. Конец синхронного кода
// 3. Микротаск 1 (Promise)
// 4. Микротаск 2 (Promise цепочка)
// 5. Микротаск 3 (queueMicrotask)
// 6. RAF (перед макротаском)
// 7. Макротаск 1 (setTimeout)
// 8. Микротаск в макротаске
Микротаск vs Макротаск
| Микротаск | Макротаск |
|---|---|
| Promise.then/catch | setTimeout |
| queueMicrotask() | setInterval |
| async/await | setImmediate (Node.js) |
| MutationObserver | requestAnimationFrame |
| I/O операции |
Ключевое различие: ВСЕ микротаски выполняются перед первым макротаском.
Практическое применение
Проблема: Race condition
// Неправильно: используется setTimeout
function updateData() {
setTimeout(() => {
data = newValue;
render();
}, 0);
}
// Правильно: используется микротаск
function updateData() {
queueMicrotask(() => {
data = newValue;
render();
});
}
Кэширование результатов
class DataFetcher {
constructor() {
this.cache = new Map();
this.pendingMicrotasks = new Map();
}
async fetch(id) {
if (this.cache.has(id)) {
return this.cache.get(id);
}
// Все запросы за один Event Loop цикл будут батчированы
if (!this.pendingMicrotasks.has(id)) {
this.pendingMicrotasks.set(id,
queueMicrotask(() => this.batchFetch())
);
}
return this.pendingMicrotasks.get(id);
}
batchFetch() {
const ids = Array.from(this.pendingMicrotasks.keys());
// Одна batch сеть операция для всех
}
}
Отладка микротасков
// Chrome DevTools поддерживает отладку микротасков
// Нужно включить:
// 1. DevTools -> Settings -> Experiments
// 2. Поищи 'track async stack traces'
// 3. Включи опцию
function trackMicrotask(name) {
console.time(name);
queueMicrotask(() => {
console.timeEnd(name);
});
}
trackMicrotask('important-microtask');
Лучшие практики
- Используй queueMicrotask() когда нужен явный микротаск
- Избегай setTimeout(fn, 0) для чувствительных по времени операций
- Понимай Event Loop для предсказуемого поведения
- Документируй микротаски если они критичны
- Тестируй порядок выполнения особенно с async операциями
// Хорошо: явно и читаемо
queueMicrotask(() => {
updateUI();
});
// Плохо: неясно что происходит
setTimeout(() => {
updateUI();
}, 0);
Микротаски — это фундаментальная часть Event Loop JavaScript. Понимание их поведения критично для написания предсказуемого асинхронного кода.