Введение
В современном мире веб-разработки часто возникают ситуации, когда необходимо эффективно управлять большим объемом данных и производить сложные вычисления. Однако выполнение таких задач в основном потоке UI может привести к замедлению работы приложения и ухудшению пользовательского опыта. Вот здесь на помощь приходят веб-воркеры.
Веб-воркеры позволяют выполнять сложные вычисления в фоновом потоке, освобождая основной поток для работы с пользовательским интерфейсом. Однако управление веб-воркерами и передача данных между ними и основным потоком может быть непростой задачей.
В этой статье мы рассмотрим реализацию шины, которая облегчает управление данными и вычислениями между веб-воркерами и основным потоком. Мы обсудим архитектуру системы, ключевые компоненты и преимущества такого подхода. Также мы рассмотрим, как эта шина может быть использована в реальных приложениях для улучшения производительности и удобства разработки.
Для прочтения этой статьи предполагается, что вы уже имеете опыт использования typescript и postMessage, т.к. я не буду вдаваться в подробности этих технологий.
Определение терминов и концепций
Основной поток в контексте веб-разработки — это поток, в котором выполняется большая часть JavaScript-кода, включая обработку событий, обновление пользовательского интерфейса и так далее. Он отвечает за выполнение всего кода, который влияет на то, что пользователь видит в браузере.
Веб-воркер — это скрипт, который браузер может выполнить в фоновом потоке, отдельно от скрипта, который выполняется на веб-странице, в фоновом потоке. Это позволяет выполнение сложных вычислений без блокировки основного потока.
Шина (bus) в данном контексте представляет собой механизм, который обеспечивает обмен данными и командами между различными частями системы, такими как веб-воркеры и основной поток.
Сервис — это модуль или компонент, который предоставляет определенную функциональность в системе. В контексте шины, сервис может быть представлен в виде веб-воркера, который выполняет определенные вычисления или обрабатывает данные.
Фабрика сервисов — это механизм, позволяющий создавать экземпляры сервисов с определенной конфигурацией. В случае шины, фабрика сервисов может быть использована для создания экземпляров веб-воркеров с необходимыми параметрами и настройками.
Сервис пустышка (Mock Service) — это прокси-класс, который используется в основном потоке для вызова методов веб-воркера. Он облегчает взаимодействие с веб-воркером, делая его прозрачным для основного потока.
ReturnType — это перечисление, определяющее режим работы сервиса (работает ли он с Promise или с Observable объектами).
Архитектура и работа шины
Шина в данном контексте представляет собой систему, которая обеспечивает взаимодействие между основным потоком и веб-воркерами. Вот как это работает:
1. Регистрация и инициализация веб-воркеров:
Веб-воркеры регистрируются в шине с помощью специальных методов. Эти воркеры могут быть инициализированы в момент первого вызова любого метода из любого сервиса этого воркера.
2. Создание фабрики сервисов:
Фабрика сервисов создается на основе зарегистрированного веб-воркера. Она позволяет создавать экземпляры сервисов с нужными параметрами и настройками.
3. Использование сервисов пустышек:
С помощью фабрики сервисов создаются сервисы пустышки, которые используются в основном потоке. Они делают взаимодействие с веб-воркером прозрачным, поскольку предоставляют тот же API, что и реальный сервис в веб-воркере.
4. Отправка сообщений в веб-воркер:
Когда метод на сервисе пустышки вызван, создается команда на отправку сообщения в веб-воркер. Эта команда содержит информацию о методе, аргументах и других параметрах.
5. Получение результатов из веб-воркера:
Веб-воркер обрабатывает полученное сообщение и отправляет результат обратно. В зависимости от режима работы сервиса (ReturnType), результат может быть представлен как Promise или Observable.
6. Управление ресурсами:
После получения результатов, шина управляет ресурсами, освобождая ненужные подписки и очереди.
Схема работы шины:
На схеме слева изображен основной поток, справа — веб-воркеры. Реальный экземпляр сервиса расположен в веб-воркере, а в основном потоке находится его мок. Когда мы вызываем метод, мок сервис перенаправляет этот вызов в часть шины в основном потоке, которая, в свою очередь, перенаправляет его в часть шины в веб-воркере согласно настройкам регистрации воркера в шине. Затем вызов передается реальному экземпляру сервиса. Ответ от сервиса направляется в основной поток по той же логике. На уровне библиотеки используется технология postMessage для передачи сообщений, но вы можете реализовать свою логику и передать ее в шину.
Примеры и практическое применение
Давайте рассмотрим некоторые примеры, чтобы лучше понять, как работает данная система в реальных условиях. Это позволит нам увидеть, как можно использовать архитектуру и инструменты в разных сценариях.
Классы библиотеки:
// Класс для регистрации веб-воркеров и создания фабрики сервисов пустышек class MainThreadBus { // Регистрация веб-воркеров registerBusWorkers(transports: ITransport[]) { /* ... */ } // Создание фабрики сервисов пустышек createFactoryService(transport: ITransport) { /* ... */ } } // Класс для регистрации реального сервиса в шине на стороне веб-воркера class BusWorker { getService!: ServiceGetter; transport!: ITransport; // Подключение к шине static connectToBus(transport: ITransport, getService: ServiceGetter, initHandler?: InitEventHandler) { /* ... */ } } // Транспортный уровень, использующий postMessage export class ObjectCopyTransport implements ITransport { constructor(private readonly ctx: Worker) { /* ... */ } // Обработчик сообщений protected messageHandler(event: MessageEvent<SendCommand>): void { /* ... */ } // Отправка сообщения sendMsg(msg: unknown): void { this.ctx.postMessage(msg); } }
Предположим, у нас есть UserService, который долго возвращает комментарии пользователя. Вынесем их получение в веб-воркер, используя эту шину. Код на стороне основного потока:
import { MainThreadBus, ObjectCopyTransport } from 'web-worker-bus'; // Создание веб-воркера const worker = new Worker(new URL('./Services/UserWorker', import.meta.url)); const userTransport = new ObjectCopyTransport(worker); // Регистрация воркера MainThreadBus.instance.registerBusWorkers([userTransport]); // Создание фабрики, привязанной к воркеру export const userWorkerFactory = MainThreadBus.instance.createFactoryService(userTransport);
Создание фабрики, привязанной к воркеру UserWorker.
Код в веб-воркере:
import { BusWorker, ObjectCopyTransport, ServiceGetter } from 'web-worker-bus'; import { container } from './UserWorkerContainer'; /* eslint-disable-next-line no-restricted-globals */ const worker = self as unknown as Worker; const serviceGetter: ServiceGetter = (serviceName) => { // Возвращаем экземпляр сервиса UserService, используя любой контейнер или создавая экземпляр класса напрямую return container[serviceName as keyof typeof container]; }; // Подключение веб-воркера к шине BusWorker.connectToBus(new ObjectCopyTransport(worker), serviceGetter);
Реализация для сервиса, который использует Observable:
// Сервис для получения комментариев пользователя export class UserServiceWithObservable { public getUserComments(): Observable<UserComments[]> { /* ... */ } }
Создание сервиса пустышки в основном потоке, который связан с сервисом в веб-воркере:
const userService = userWorkerFactory<UserService>("UserService", ReturnType.rxjsObservable);
Теперь мы можем использовать сервис, как будто бы его экземпляр находится в основном потоке.
Готовые примеры вместе с популярными фреймворками можете увидеть, перейдя по ссылкам ниже:
Обратите внимание, для Angular пришлось создать NgObjectCopyTransport — транспортный уровень, который оборачивает обработку полученных сообщений от веб-воркеров в NgZone для корректной работы рендера Angular.
Основные преимущества
-
Инкапсуляция логики: Все взаимодействие с веб-воркерами скрыто за абстракцией, делая код более чистым и удобным для чтения и поддержки.
-
Переносимость: Ваша система позволяет легко переносить тяжелые вычислительные задачи в веб-воркеры, не меняя остальной части кода. Это улучшает производительность и делает приложение более отзывчивым.
-
Гибкость: Благодаря фабрике сервисов пустышек и возможности создавать различные транспортные уровни, данная библиотека может быть использована в различных сценариях и с разными фреймворками.
-
Поддержка Observable и Promise: Встроенная поддержка наблюдаемых и обещаний облегчает интеграцию с современными приложениями и практиками программирования.
Применение
-
Улучшение производительности: В случае, если ваше веб-приложение включает в себя сложные вычисления или обработку больших объемов данных, перенос этих задач в веб-воркеры может существенно улучшить отклик и общую производительность.
-
Работа с большими данными: При работе с большими объемами данных, такими как анализ или визуализация, использование веб-воркеров с вашей библиотекой поможет обрабатывать данные эффективнее и быстрее.
-
Реальное время и потоковая обработка: Ваша библиотека позволяет легко интегрироваться с потоковой обработкой и реальным временем, такими как наблюдаемые веб-сокеты или другие потоковые источники данных.
-
Масштабируемость: Система удобна для масштабирования и может быть легко расширена для поддержки дополнительных сервисов и воркеров.
-
Платформенная независимость: Поддержка различных транспортных уровней и интеграция с популярными фреймворками делает библиотеку применимой в различных платформах и средах.
Ограничения первой версии шины
Хотя библиотека предлагает множество преимуществ, есть и некоторые ограничения, которые следует учитывать:
-
Не поддерживаются методы, созданные через symbol: Это может влиять на некоторые особенности и паттерны проектирования, которые вы можете использовать в своем коде.
-
Транспортный уровень использует копирование сообщения при передаче данных между потоками: Это может увеличить время выполнения кода, в зависимости от объема передаваемых данных. Необходимо учитывать это при работе с большими объемами данных.
-
Работает исключительно с асинхронным API: Все взаимодействия с библиотекой должны быть асинхронными. Это не должно быть большой проблемой для современных веб-приложений, но все же стоит иметь в виду.
-
Можно использовать в рамках одного сервиса только Promise или только RxJs Observable объектами: Это ограничивает гибкость в выборе подходов в разных частях одного и того же сервиса, но обеспечивает последовательность и согласованность в использовании.
Эти ограничения не являются критическими, но они могут влиять на то, как вы будете использовать библиотеку в своем проекте. Они также могут быть адресованы в будущих версиях библиотеки.
Заключение
В этой статье мы рассмотрели подход к управлению веб-воркерами с использованием шины. Этот подход предлагает решение для организации обмена сообщениями между веб-воркерами и основным потоком, обеспечивая чистую и модульную архитектуру.
Главные преимущества этого подхода включают в себя:
-
Улучшение производительности путем распределения нагрузки на разные потоки.
-
Возможность создания более чистого и модульного кода.
-
Повышение масштабируемости и гибкости в разработке.
Однако также существуют и определенные ограничения, которые следует учитывать при работе с библиотекой.
Этот подход может быть особенно полезен для разработки сложных и высоконагруженных веб-приложений, где необходимо эффективно распределять нагрузку и управлять асинхронными операциями.
Если вы заинтересованы в дальнейшем изучении этого подхода или хотите попробовать его в своем проекте, вы можете обратиться к следующим ресурсам:
Этот подход представляет собой интересное и перспективное направление в разработке веб-приложений и может стать важным инструментом в арсенале современного веб-разработчика.
Если вы заинтересованы в этом проекте, ваш вклад будет очень ценен. Вы можете помочь улучшить библиотеку, исправить ошибки, добавить новые функции или даже написать документацию. Любые предложения и пул-реквесты приветствуются! 🙂
А если у вас возникнут вопросы или что-то окажется непонятным, не стесняйтесь обращаться. Может быть, есть какой-то пункт, который хотели бы разобрать подробнее? Пишите в комментариях. Ваше мнение и интерес к деталям очень важны для меня, и я постараюсь предоставить всю необходимую информацию.
Спасибо за внимание к статье!
ссылка на оригинал статьи https://habr.com/ru/articles/752526/