Как браузер понимает, когда можно брать задачу из стека?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как браузер понимает, когда можно брать задачу из стека?
Этот вопрос про Event Loop (цикл событий) в JavaScript — механизм, который контролирует выполнение кода и обработку асинхронных операций. Event Loop постоянно проверяет, когда можно выполнить следующую задачу.
Основные очереди в Event Loop
JavaScript использует несколько очередей для управления задачами:
┌─────────────────────────────────────────┐
│ Call Stack (Стек вызовов) │ Текущий выполняющийся код
└─────────────────────────────────────────┘
↓ (когда пустой)
┌─────────────────────────────────────────┐
│ Microtask Queue (Микротаски) │ Promise.then(), queueMicrotask()
├─────────────────────────────────────────┤
│ Macrotask Queue (Макротаски) │ setTimeout(), setInterval(), fetch()
└─────────────────────────────────────────┘
Правила Event Loop
1. Call Stack всегда имеет приоритет
Event Loop проверяет в следующем порядке:
- Если Call Stack не пустой - выполнять код в нём
- Если Call Stack пустой - проверить Microtask Queue
- Если Microtask Queue не пустой - выполнить ВСЕ микротаски
- Если Microtask Queue пустой - выполнить одну макротаску
- После макротаски - снова проверить Microtask Queue
console.log('1. Start'); // Call Stack
setTimeout(() => {
console.log('4. setTimeout');
}, 0); // Macrotask
Promise.resolve()
.then(() => console.log('2. Promise')) // Microtask
.then(() => console.log('3. Promise 2'));
console.log('5. End'); // Call Stack
// Порядок вывода:
// 1. Start
// 5. End
// 2. Promise
// 3. Promise 2
// 4. setTimeout
Микротаски (Microtasks)
Что входит в Microtask Queue:
Promise.then(),.catch(),.finally()queueMicrotask(callback)MutationObserver
Особенность: Все микротаски выполняются ДО следующей макротаски:
setTimeout(() => console.log('MacroTask 1'), 0);
Promise.resolve()
.then(() => console.log('MicroTask 1'))
.then(() => console.log('MicroTask 2'));
queueMicrotask(() => console.log('MicroTask 3'));
setTimeout(() => console.log('MacroTask 2'), 0);
// Порядок:
// MicroTask 1
// MicroTask 2
// MicroTask 3
// MacroTask 1
// MacroTask 2
Макротаски (Macrotasks)
Что входит в Macrotask Queue:
setTimeout()setInterval()setImmediate()fetch()requestAnimationFrame()(спорно, зависит от браузера)- Обработчики событий (click, load и т.д.)
Особенность: Выполняется одна за раз, потом Event Loop проверяет микротаски:
setTimeout(() => {
console.log('MacroTask 1');
Promise.resolve().then(() => console.log('MicroTask inside Macro'));
}, 0);
setTimeout(() => {
console.log('MacroTask 2');
}, 0);
// Порядок:
// MacroTask 1
// MicroTask inside Macro <- Микротаски выполняются сразу после макротаски
// MacroTask 2
Детальный пример Event Loop
console.log('Script start');
setTimeout(() => {
console.log('setTimeout 1');
Promise.resolve().then(() => console.log('Promise in setTimeout'));
}, 0);
Promise.resolve()
.then(() => {
console.log('Promise 1');
setTimeout(() => console.log('setTimeout in Promise'), 0);
})
.then(() => console.log('Promise 2'));
queueMicrotask(() => console.log('queueMicrotask'));
console.log('Script end');
// Пошагово:
// 1. Call Stack: выполнить синхронный код
// -> "Script start"
// -> "Script end"
// 2. Microtask Queue: все Promise и queueMicrotask
// -> "Promise 1"
// -> "queueMicrotask"
// -> "Promise 2"
// 3. Macrotask Queue: setTimeout
// -> "setTimeout 1"
// 4. Проверить Microtask Queue после макротаски
// -> "Promise in setTimeout"
// 5. Macrotask Queue: второй setTimeout
// -> "setTimeout in Promise"
// Полный порядок:
// Script start
// Script end
// Promise 1
// queueMicrotask
// Promise 2
// setTimeout 1
// Promise in setTimeout
// setTimeout in Promise
Практический пример: Когда браузер перерисовывается?
// Браузер перерисовывает между макротасками
const box = document.getElementById('box');
// 1. Макротаска
setTimeout(() => {
box.style.background = 'red';
// Сразу же микротаска НЕ вызывает перерисовку
Promise.resolve().then(() => {
console.log('Microtask: background уже красный');
});
}, 0);
// 2. Вторая макротаска - ЗДЕСЬ произойдёт перерисовка между ними
setTimeout(() => {
box.style.background = 'blue';
console.log('Вторая макротаска');
}, 0);
// Результат:
// 1. Первая макротаска: красный фон
// Microtask: background уже красный
// 2. ПЕРЕРИСОВКА (box становится красным)
// 3. Вторая макротаска: синий фон
// 4. ПЕРЕРИСОВКА (box становится синим)
Как использовать знания о Event Loop?
1. Используй Promise вместо setTimeout для срочных задач
// Выполнится раньше
Promise.resolve().then(() => console.log('Urgent'));
// Выполнится позже
setTimeout(() => console.log('Not urgent'), 0);
2. Группируй операции DOM в одну макротаску
// ПЛОХО - перерисовка много раз
for (let i = 0; i < 1000; i++) {
element.style.width = i + 'px'; // Каждая итерация может вызвать перерисовку
}
// ХОРОШО - все изменения в одной макротаске
setTimeout(() => {
for (let i = 0; i < 1000; i++) {
element.style.width = i + 'px';
}
}, 0);
3. Используй requestAnimationFrame для анимаций
// requestAnimationFrame синхронизируется с перерисовкой браузера
function animate() {
element.style.transform = `translateX(${x}px)`;
x += 5;
if (x < 500) {
requestAnimationFrame(animate);
}
}
animate();
Инструменты для отладки Event Loop
// Визуализируем порядок выполнения
function log(message) {
const timestamp = new Date().toLocaleTimeString();
console.log(`[${timestamp}] ${message}`);
}
log('Start');
setTimeout(() => log('setTimeout'), 0);
Promise.resolve().then(() => log('Promise'));
queueMicrotask(() => log('Microtask'));
log('End');
// Вывод покажет точный порядок
Выводы
Браузер понимает, когда брать задачу из стека на основе:
- Call Stack пустой? Проверить Microtask Queue
- Microtask Queue не пустой? Выполнить ВСЕ микротаски
- Microtask Queue пустой? Взять одну макротаску
- После макротаски снова проверить Microtask Queue
Этот порядок гарантирует, что Promise.then() всегда выполнится раньше setTimeout(), что критично для корректного поведения асинхронного кода в JavaScript.