Недавно мы экспериментировали с построением различных графиков в реагирующем приложении с помощью Plotly.js. Довольно быстро стало понятно, что работать с большими наборами данных в основном потоке приложения просто невозможно. Перетаскивание ползунка диапазона дат для фильтрации графиков вызывает раздражение, а не гладкость, к которой мы стремимся.

Введите Вебворкеры.

WebWorkers позволяют переносить задачи, интенсивно использующие процессор, на другие потоки и поддерживаются практически всеми современными веб-браузерами. Однако они представляют собой механизм очень низкого уровня, предоставляющий для связи только простой API передачи сообщений. Пакет webpack-worker предоставляет нам более чистую и интуитивно понятную абстракцию, основанную на обещаниях.

Настройка сцены

Чтобы продемонстрировать, мы собираемся построить график, который показывает 10 лучших движений из некоторых исторических данных фондового рынка, доступных на http://pages.swcp.com/stocks/. Мы также собираемся добавить ползунок диапазона дат, чтобы динамически выбирать диапазон дат для анализа.

Данные состоят из цен на 242 акции за год в формате CSV, по одной строке на акцию в день. Например:

20090916,AMZN,85.97,90.98,85.9,90.7,131142

Это дата, символ акции, цена открытия, максимум, минимум и цена закрытия, я не уверен, к чему относится последний столбец. Мы собираемся продублировать данные, чтобы у нас фактически были данные за 5 лет. Это разрушает целостность результатов, но мы здесь не для того, чтобы анализировать акции!

Приложению предстоит выполнить значительный объем работы, чтобы преобразовать данные этого стиля в нужный нам формат, особенно при работе с данными за многие годы. Детали реализации не относятся к этому посту, но полный пример доступен в репозитории github.

Вы можете увидеть рабочий пример, который сравнивает использование WebWorkers с запуском всего в основном потоке пользовательского интерфейса здесь.

Определение нашего рабочего кода

Пакет webpack-worker предоставляет простые API, основанные на обещаниях для объявления и использования рабочего кода. Он предоставляет модели как для одиночных длительных процессов, так и для API, которые можно вызывать несколько раз. Мы собираемся использовать модель API, так как нам нужно будет пересчитывать данные графика, когда пользователь перетаскивает ползунок.

Основываясь на наших требованиях выше, наш работник может выглядеть примерно так:

Вы заметите, что этот модуль ничего не экспортирует. Это связано с тем, что веб-воркеры работают в изолированном потоке — этот модуль является точкой входа для веб-воркеров. webpack-worker упаковывает возвращенный API и обрабатывает для нас низкоуровневую связь. Мы поговорим о том, как использовать webpack для создания бандла для нашего воркера чуть позже.

Потребление нашего работника

webpack-worker генерирует для нас клиентский API на основе API, который мы указываем в worker. Обратите внимание, что в настоящее время для вызовов API и инициализации может быть предоставлен только один аргумент.

Давайте посмотрим, как наш компонент графика может выглядеть без фильтров. Мы добавим их дальше.

Довольно простые вещи. Вы заметите, что мы разделили логику рендеринга графика в функции renderGraph. Мы будем использовать это повторно, когда добавим фильтр даты. В итоге это выглядит примерно так:

Динамическая фильтрация

Для простого решения нашего пользовательского интерфейса мы будем использовать пакет react-input-range NPM. Добавим его в наш компонент:

В итоге это выглядит примерно так:

Вещи начинают выглядеть довольно хорошо! Теперь, когда мы перетаскиваем ползунок даты, график обновляется.

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

Дросселирование

К счастью, webpack-worker дает нам простой способ ограничить наши запросы, чтобы обеспечить максимально быстрое поведение. Применить его просто:

Функция applyTo прикрепляет дроссель ко всем функциям-членам предоставленного объекта. При дросселировании, если запросы выполняются, когда рабочий процесс уже занят, в очередь ставится только самый последний запрос, а все остальные отбрасываются.

Когда запросы отбрасываются, возвращенное обещание отклоняется, поэтому нам нужно обработать отклонение в нашей функции renderGraph:

Настройка веб-пакета

Как уже говорилось, рабочие процессы — это изолированные потоки, которым требуется собственная точка входа. Соответственно, мы можем создавать точки входа в нашей конфигурации webpack. Это простой вопрос создания рабочего узла в хэше записи, например:

Если ваше приложение было создано с помощью create-react-app, есть пара дополнительных шагов, которые описаны здесь.

Подведение итогов

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

Вариант использования, обсуждаемый здесь, предназначен исключительно для демонстрации влияния использования WebWorkers — webpack-worker можно использовать для выполнения любого кода Javascript в WebWorker, а не только для вычисления данных графика. Также стоит отметить, что webpack-worker будет работать с другими системами связывания, такими как browserify.

Полный исходный код этого примера доступен в репозитории webpack-worker здесь.

Первоначально опубликовано на gist.github.com.