Поставили await. Интерфейс всё равно висит 😩

Задача простая. Убрать зависание страницы при обработке большого массива данных.

Разработчик нашёл тяжёлый цикл, добавил async, расставил await и отправил на ревью. В описании задачи появилась приятная формулировка: «Оптимизирована обработка данных, устранена блокировка интерфейса».

Код выглядит убедительно:

async function calculate(items) {  for (const item of items) {   await Promise.resolve()   processItem(item)  } }

Асинхронная функция, промисы, выполнение приостанавливается на каждой итерации. Осталось проверить.

Нажимаем кнопку. Страница замирает. Индикатор загрузки не появляется. Через пару секунд интерфейс оживает.

Зато теперь асинхронно 👏🏻

В ECMAScript await действительно приостанавливает выполнение асинхронной функции. Её продолжение организуется через задания промисов. Но ECMAScript ничего не обещает про отрисовку кадров.

За браузерный event loop отвечает HTML Standard.

В нём определены task queues, microtask queue и механизм microtask checkpoint.

Обычные задачи выбираются из task queues. А во время контрольной точки браузер выполняет микрозадачи, включая те, которые добавились в процессе выполнения предыдущих.

Наш await Promise.resolve() как раз приводит к постановке продолжения функции в очередь микрозадач.

Одна итерация закончилась, следующая уже ждёт. И так до конца массива.

Пока выполняется эта непрерывная цепочка, браузер не переходит к обновлению отображения.

Получается занятно. Мы тысячи раз приостановили функцию, но так и не дали интерфейсу обновиться.

Индикатор загрузки честно включили. Состояние компонента поменяли. Пользователь индикатора не увидел.

Можно разбить вычисления на порции и планировать их через setTimeout. Между порциями у браузера появится возможность заняться другими задачами и отрисовкой. Хотя гарантии нового кадра после каждого таймера нет.

Можно вынести тяжёлую обработку в Web Worker. Главный поток освободится от вычислений, но появятся вопросы передачи данных и получения результата.

А можно сначала выяснить, зачем мы вообще обрабатываем такой объём данных на главном потоке.

Вариантов хватает. Но ни один не сводится к добавлению слова async.

Особенно прикольно, когда после такой оптимизации кто-нибудь предлагает поставить ещё один await. Для надёжности.

В задаче блокировку устранили. Браузеру об этом пока не сообщили.

Поставили await. Интерфейс всё равно висит 😩 | Сетка — социальная сеть от hh.ru