Как избежать зависания интерфейса при долгих вычислениях в однопоточном JavaScript
Что проверяют
Понимаете ли вы, как связаны event loop и отрисовка: пока основной поток занят синхронным кодом, браузер не обрабатывает клики и не рисует новый кадр. Следом обычно просят показать, как разбить задачу на части или вынести её в воркер.
Как отвечать
Начните с механики: браузер рисует кадр между задачами, задача дольше 50 мс считается длинной (long task) и напрямую ухудшает INP. Дальше способы по возрастанию усилий:
- ускорить сам алгоритм: убрать квадратичные проходы, мемоизировать, обрабатывать только видимое (виртуализация списков). Часто этого хватает;
- дробление на порции: обработать кусок данных и уступить поток через
await scheduler.yield()там, где он поддерживается, или черезawait new Promise(r => setTimeout(r, 0))как запасной вариант. Между порциями браузер успевает обработать ввод и отрисовать кадр; requestIdleCallback: для некритичной фоновой работы в моменты простоя;- Web Worker: настоящий отдельный поток. Данные уходят через
postMessageсо структурным клонированием, большиеArrayBufferлучше передавать как transferable, без копирования. Удобная обёртка: библиотекаComlink. Доступа к DOM в воркере нет; - WebAssembly в воркере: для тяжёлой математики и обработки изображений.
В React добавьте useTransition и useDeferredValue: вычисление они не ускоряют, но позволяют прервать низкоприоритетный рендер ради ввода пользователя.
Где спотыкаются
Вставляют setTimeout(() => {}, 0) внутрь цикла и считают, что поток освобождён. Цикл продолжает крутиться синхронно, пустой колбэк просто встаёт в очередь. Уступить поток можно, только прервав цикл и продолжив его в следующей задаче.
Путают асинхронность с параллельностью. async-функция с тяжёлым синхронным телом блокирует поток точно так же, а микрозадачи выполняются до отрисовки, поэтому цепочка Promise.then кадру не поможет.
Предлагают setImmediate, которого в браузерах нет, это API Node.js. И забывают о цене воркера: копирование большого объекта через postMessage само по себе может занять десятки миллисекунд.