Что произойдет с интерфейсом при бесконечном цикле создания микротасков в JavaScript
Что проверяют
Понимаете ли вы, что очередь микрозадач разгребается целиком и до отрисовки, и отличаете ли такое зависание от обычного синхронного цикла. Вопрос про приоритеты очередей, но заданный через симптом.
Как отвечать
Очередь микрозадач опустошается до того, как браузер вернётся к макрозадачам и к кадру, причём задачи, добавленные во время разгребания, попадают в тот же проход. Поэтому код ниже управление не отдаёт никогда:
function loop() {
Promise.resolve().then(loop);
}
loop();Для интерфейса это значит: кадр не рисуется, requestAnimationFrame не вызывается, клики и ввод копятся необработанными, таймеры не срабатывают, через некоторое время вкладка предлагает закрыть страницу как не отвечающую. Снаружи отличить от while (true) невозможно, в профайлере видна одна непрерывная длинная задача.
Лечится не добавлением await, а сменой очереди: работа режется на порции, между которыми управление уступается макрозадачей (setTimeout, MessageChannel, scheduler.yield там, где он доступен), а тяжёлые вычисления уезжают в Web Worker, где главный поток не при чём. Полезно назвать границу: await на уже решённом промисе это микрозадача, а await fetch(...) ждёт сеть и управление отдаёт, поэтому цикл с реальным ожиданием интерфейс не вешает.
Где спотыкаются
Предлагают setTimeout как лекарство, не меняя саму рекурсию. Пока микрозадачи порождают сами себя, до таймеров дело не дойдёт, колбэк просто не вызовется, и совет из общих соображений оказывается бесполезным.
Путают приоритет с параллелизмом и говорят, что микрозадачи асинхронные, а значит ничего не блокируют. Асинхронность задаёт порядок исполнения, а не второй поток: и промисы, и отрисовка живут в одном главном потоке.
Ищут причину в React и оптимизируют компоненты, хотя виноват рекурсивный then в подписке или в обработчике события. Различить просто: при лишних перерисовках профайлер показывает много коротких задач, а здесь одна бесконечная, и замирает вообще всё, включая анимации на чистом CSS, зависящие от главного потока.