В данной статье я хотел бы привести пример практического кейса использования метода requestIdleCallback, который возник у меня на проекте. Кейс сам по себе небольшой, замеры времени отработки функции и отрисовки компонентов для использования производились с помощью React Profiler.

Хотелось бы сразу сказать, что статья может быть не чем-то новым, но может оказаться полезной в плане практического понимания того, где может пригодиться requestIdleCallback и как он может быть использован.

Коротко о проекте

Чтобы внести контекст, я упрощенно опишу проект в той части, с которой пришлось работать.

Это поисковик по различным материалам, выдача которых производится в таблице, количество столбцов и строк в которой можно регулировать. Соответственно, как и во всех поисковиках, присутствуют фильтры, которые организованы у нас в выезжающем по нажатию кнопки "Фильтры" сайдбару. В сайдбаре присутствуют несколько разворачивающихся списков с категориями фильтров (Основные, Параметр и т.п.) в которых лежат сами фильтры (тоже в свернутом виде, но с ними проблем нет). Фильтры могут быть разных типов: числовые, обычным списком чекбоксов для выбора, дерево фильтров (иерархия с зависимостями от родительского фильтра) и т.п.

Для наглядности скрин сайдбара с фильтрами

Теперь к делу

Недавно на проекте появилось немного свободного времени и на меня упала задача прошерстить код и выделить части проекта, которые необходимо отрефакторить и выписать все это в отдельную задачу тех. долга (и возможно что-то сразу пофиксить).

Я, недолго думая, пошел смотреть фильтры, так как знал, что там могут быть определенные проблемы. Это связано с тем, что когда-то давно, когда я только пришел на проект с ними уже была одна проблема. Она заключалась в том, что при большом количестве фильтров сайдбар после нажатия открывался через время, которое было чуть больше секунды, что очень долго.

Открытие сайдбара в самом начале
Открытие сайдбара в самом начале

Причина была в том, что происходил расчет (распределение по типам и раскидывания данных по компонентам для каждого типа) и отрисовка даже в свернутых категориях фильтров. Такой подход помогал заранее рассчитать все категории фильтров и разворачивать их без задержки, но когда фильтров стало много вылезла такая проблема.

Упрощенный код того, как все происходило
Упрощенный код того, как все происходило

Поправил я это достаточно просто на тот момент - поставил расчет с отрисовкой по условию, что если открыт, то считай. Открываться стало около 100мс, что очень хорошо.

После того, как поставили условие
После того, как поставили условие
Упрощенный код того, как все происходило
Упрощенный код того, как все происходило

Сделав так, я пожертвовал расчетами, которые происходили заранее и, как итог, замедлил открытие самих категорий, так как теперь расчеты происходили при их открытии, а значит появилась небольшая задержка перед тем, как список развернется. В тот момент, когда я это делал, фильтров было много, но должно было стать еще больше, так что решение было временным.

Время пришло...

И вот настал момент, когда появилось время на рефакторинг и когда фильтров начало становиться больше и в ближайшее время обещало увеличиться в достаточно большое количество (так как количество материалов с разными параметрами увеличивалось).

Один из самых многочисленных категорий фильтров - это Параметр (на скрине в спойлере в самом начале категория идет второй и по умолчанию свернута). При нажатии на нее расчет и открытие занимает около 100мс (да, на данный момент вроде как не так много, но небольшая задержка видна глазу, а если количество фильтров в категории увеличится, то и задержка вырастет).

Открытие категории
Открытие категории

Я начал думать, как можно решить данную проблему. И я придумал вариант, как можно это исправить - надо рассчитывать открытую категорию перед открытием сайдбара (как сейчас и происходит), а остальные категории рассчитывать не при нажатии на них, а заранее, после того, как сайдбар откроется.

Как это можно сделать?

Если чуть углубиться, то вся проблема возникает из-за того, что расчеты блокируют работу браузера и из-за этого все задержки. Значит надо отложить расчеты закрытых категорий, пока сайдбар не откроется.

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

Каким образом я его применил?

Я вынес маппинг данных с расчетами и передачей в отрисовывающийся компонент в отдельную функцию, которая помещает данные в переменную, которая уже помещалась в код для отрисовки. И в зависимости от того, открыта ли категория, функция выполнялась либо сразу, либо помещалась в requestIdleCallback.

Упрощенный код с requestIdleCallback
Упрощенный код с requestIdleCallback

При таком подходе мы получили то, что теперь категория рассчитывалась заранее и стала открываться около 3мс.

Открытие категории после применения requestIdleCallback
Открытие категории после применения requestIdleCallback

Что в итоге?

В итоге мы получили то, что фильтры открываются на данный момент достаточно быстро и взаимодействие с ними при открытии различных категорий не затормаживается, а значит не вызывает визуального дискомфорта пользователей. Раньше категории разворачивались около 100мс, а сейчас около 3мс, а значит взаимодействие ускорилось где-то в 30 раз и как бы не казалось, что это маленькие цифры (хоть и порядок разницы большой), в будущем это может существенно сыграть в нашу пользу.

На данный момент requestIdleCallback все еще не поддерживается в Safari, но в нашем случае это не проблема, так как на проект вход идет с внутреннего контура и IOS там даже не пахло) В случае, если же все-таки есть пользователи с Safari я бы наверное попробовал провернуть что-то такое с использованием setTimeout.

Надеюсь эта статья будет кому-нибудь полезна для понимания использования и влияния requestIdleCallback и буду еще больше рад, если вы сможете привести дополнительные примеры использования данного метода.

Комментарии (4)


  1. ruslan_astratov
    06.09.2023 15:51
    +1

    Спасибо. Было полезно!


  1. xeleos
    06.09.2023 15:51

    На просторах гитхаба для сафари нашёл что-то типо полифила, хотя работает оно скорее всего не так же, т.к. там используется setTimeout.


    1. idmx Автор
      06.09.2023 15:51

      Да, тоже его видел, там действительно на setTimeout и это скорее на данный момент единственное похожее решение (по крайней мере я других не вижу пока что). Когда нибудь и они поддержат этот метод:)


  1. Keleborn379
    06.09.2023 15:51

    Отличная статья