Event loop подробно
Event loop — механизм, позволяющий однопоточному JavaScript выполнять асинхронные операции.
Компоненты
- Call stack — стек вызовов функций
- Web APIs — таймеры, сеть, DOM (в браузере)
- Microtask queue — очередь микрозадач
- Macrotask queue — очередь макрозадач
- Event loop — перекладывает задачи
Порядок выполнения
- Синхронный код выполняется до конца
- Все микрозадачи
- Одна макрозадача
- Снова все микрозадачи
- Следующая макрозадача
- И так далее
Микрозадачи
Promise.then,catch,finallyqueueMicrotaskMutationObserver
Promise.resolve().then(() => console.log('микрозадача'));
Макрозадачи
setTimeout,setIntervalsetImmediate(Node.js)- События DOM
- I/O
setTimeout(() => console.log('макрозадача'), 0);
Классический пример
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// 1, 4, 3, 2
Разбор:
- Синхронный код:
1,4 - Микрозадачи:
3 - Макрозадачи:
2
Промисы всегда раньше таймеров.
Ещё пример
setTimeout(() => console.log('timeout 1'), 0);
setTimeout(() => console.log('timeout 2'), 0);
Promise.resolve()
.then(() => console.log('promise 1'))
.then(() => console.log('promise 2'));
console.log('sync');
// sync
// promise 1
// promise 2
// timeout 1
// timeout 2
Микрозадачи добавляют новые микрозадачи
Promise.resolve()
.then(() => {
console.log('1');
Promise.resolve().then(() => console.log('2'));
})
.then(() => console.log('3'));
// 1, 3, 2
2 добавляется в конец очереди микрозадач, 3 уже там ждала.
Микрозадачи могут блокировать
Бесконечная цепочка микрозадач блокирует макрозадачи и UI:
function loop() {
Promise.resolve().then(loop);
}
loop(); // браузер зависнет
Макрозадачи так не блокируют, потому что между ними браузер может отрисовать кадр.
setTimeout(0)
Не гарантирует немедленное выполнение. Минимум ~4мс в браузере, ещё больше после вложенных таймеров.
setTimeout(() => console.log('не сразу'), 0);
requestAnimationFrame
Отдельная очередь — перед отрисовкой кадра:
requestAnimationFrame(() => console.log('перед кадром'));
Порядок: микрозадачи → requestAnimationFrame → отрисовка → макрозадачи.
Node.js event loop
У Node.js свои фазы:
- timers —
setTimeout,setInterval - pending callbacks — I/O-ошибки
- poll — I/O
- check —
setImmediate - close callbacks — закрытие
setImmediate выполняется после poll, setTimeout — в фазе timers. Порядок может отличаться.
Микрозадачи между фазами
После каждой фазы Node.js очищает микрозадачи, потом идёт к следующей.
Практика: тяжёлые задачи
Тяжёлое вычисление блокирует поток:
function heavy() {
for (let i = 0; i < 1e9; i++) {}
}
heavy(); // UI зависнет
Решения:
- Web Workers — отдельный поток
- Разбить на части через
setTimeout - requestIdleCallback — в свободное время
Итоги
- Event loop: стек → микрозадачи → макрозадачи
- Промисы раньше таймеров
- Микрозадачи могут блокировать
requestAnimationFrameперед отрисовкой- Node.js имеет свои фазы
- Тяжёлые вычисления — в Web Workers