Что такое цикл событий (event loop) и как он работает?
JavaScript является однопоточным языком программирования (движок JS в 1 единицу времени может выполнять всего 1 операцию, для этого выделяется стек). В стеке хранятся фреймы - это локальные аргументы и переменные, которые хранятся в функции.
Список событий, которые должны обрабатываться формируют очередь событий. Когда стек освобождается движок может обработать любое событие из этой очереди.
Координирование этого процесса и происходит в EventLoop
.
EventLoop
- это механизм, используемый браузером или Node.js для управления асинхронным кодом. Он позволяет синхронизировать callstack и очередь задач.
- Выбрать и исполнить старейшую синхронную задачу.
- Исполнить все микрозадачи:
- Пока очередь микрозадач пуста: - выбрать из очереди и исполнить старейшую микрозадачу
- Отрисовать изменения страницы (
requestAnimationFrame
), если они есть. - Если очередь макрозадач пуста – подождать, пока появится макрозадача.
- Перейти к шагу 1.
На основе этой схемы строится вся работа Event Loop.
Если у кого-то из заказчиков не оказалось задач, то Event Loop просто идет к следующему. И, наоборот, если у заказчика задачи занимают много времени, то остальные заказчики будут ждать своей очереди. А если задачи от какого-то заказчика оказались бесконечными, то CallStack переполняется, и браузер начинает ругаться:
_Рисунок 2 - Страница не отвечает
Если где-то произойдёт ошибка, то отказ пропустит обработчики на выполнение и долетит до ближайшего обработчика отказа, после чего цепочка продолжит работать в штатном режиме.
console.log('Start'); // Синхронный код, выполняется первым
// Микрозадача (Microtask)
queueMicrotask(() => {
console.log('Inside microtask'); // Выполняется после синхронного кода, но перед макрозадачами
});
// Макрозадача (Macrotask)
setTimeout(() => {
console.log('Inside macrotask'); // Выполняется после всех микрозадач
});
console.log('End'); // Синхронный код, выполняется вторым
/*
Ожидаемый результат выполнения:
1. Start
2. End
3. Inside microtask
4. Inside macrotask
*/
Ссылка: Иллюстрация работы событийного цикла
Разница между микро- и макрозадачами в event loop?
Микрозадачи (microtask
)
Асинхронные задачи требуют правильного управления. Для этого стандарт предусматривает внутреннюю очередь PromiseJobs
, более известную как очередь-микрозадач (microtask queue)».
Микрозадачи - это задачи, которые добавляются в очередь микрозадач, после того как стек вызовов стал пуст, но до того, как будут выполнены макрозадачи.
Микрозадачи приходят только из кода. Обычно они создаются promise : выполнение обработчика .then/catch/finally
становится микрозадачей. Микрозадачи также используются «под капотом» await
, т.к. это форма обработки промиса.
Микрозадачи включают в себя:
- обработчики промисов.
- обработчики notationObserver.
- функции добавленные с помощью queueMicrotask.
Расскажите о queueMicrotask
![[Pasted image 20230723140208.png|]]
_Рисунок 3 - Иллюстрация вызова queueMicrotask
queueMicrotask
- это JavaScript метод, который используется для добавления переданной в неё функции в очередь микрозадач, внутри EventLoop.
Макрозадачи (macrotask
)
Макрозадачи - это задачи, которые добавляются в очередь и добавляются после того как очередь задач пуста.
Задачи из очереди исполняются по правилу «первым пришёл – первым ушёл». Когда браузер заканчивает выполнение скрипта, он обрабатывает событие mousemove
, затем выполняет обработчик, заданный setTimeout
, и так далее.
Макрозадачи включают в себя:
- элементы пользовательского ввода;
- таймеры;
- загрузка и ресурсов
- и другие асинхронные задачи.
Решение задач на асинхронность
Рассмотрим примеры:
Promise.resolve()
.then(() => {
return Promise.reject(`O_o`)
})
.then(() => {
// Все обработчики будут не выполнены
})
.catch((error) => {
console.log(error) // `O_o`
})
.then(() => {
// Продолжаем выполнять цепочку
})
Возвращать значение можно и внутри catch, оно будет точно также обработано в цепочке.
Promise.reject("O_o")
.catch((error) => {
console.log(error) // O_o
return "^_^"
})
.then((value) => {
console.log(value) // ^_^
})
Важно понимать, что обработка ошибок работает только тогда, когда цепочка непрерывна. Если опустить return
и создать обещание, установленное на отказ, то последующий catch
не сможет его обработать.
Promise.resolve()
.then(() => {
Promise.reject("O_o")
})
.catch(() => {
// Будет пропушено
})