Шина между Веб-воркерами и основным потоком. Ускоряем работу JavaScript

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
Photo by Indira Tjokorda on Unsplash
Photo by Indira Tjokorda on Unsplash

Введение

В современном мире веб-разработки часто возникают ситуации, когда необходимо эффективно управлять большим объемом данных и производить сложные вычисления. Однако выполнение таких задач в основном потоке 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);

Теперь мы можем использовать сервис, как будто бы его экземпляр находится в основном потоке.

Готовые примеры вместе с популярными фреймворками можете увидеть, перейдя по ссылкам ниже:

  • React

  • Angular

  • Vue.js

Обратите внимание, для Angular пришлось создать NgObjectCopyTransport — транспортный уровень, который оборачивает обработку полученных сообщений от веб-воркеров в NgZone для корректной работы рендера Angular.

Основные преимущества

  1. Инкапсуляция логики: Все взаимодействие с веб-воркерами скрыто за абстракцией, делая код более чистым и удобным для чтения и поддержки.

  2. Переносимость: Ваша система позволяет легко переносить тяжелые вычислительные задачи в веб-воркеры, не меняя остальной части кода. Это улучшает производительность и делает приложение более отзывчивым.

  3. Гибкость: Благодаря фабрике сервисов пустышек и возможности создавать различные транспортные уровни, данная библиотека может быть использована в различных сценариях и с разными фреймворками.

  4. Поддержка Observable и Promise: Встроенная поддержка наблюдаемых и обещаний облегчает интеграцию с современными приложениями и практиками программирования.

Применение

  1. Улучшение производительности: В случае, если ваше веб-приложение включает в себя сложные вычисления или обработку больших объемов данных, перенос этих задач в веб-воркеры может существенно улучшить отклик и общую производительность.

  2. Работа с большими данными: При работе с большими объемами данных, такими как анализ или визуализация, использование веб-воркеров с вашей библиотекой поможет обрабатывать данные эффективнее и быстрее.

  3. Реальное время и потоковая обработка: Ваша библиотека позволяет легко интегрироваться с потоковой обработкой и реальным временем, такими как наблюдаемые веб-сокеты или другие потоковые источники данных.

  4. Масштабируемость: Система удобна для масштабирования и может быть легко расширена для поддержки дополнительных сервисов и воркеров.

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

Ограничения первой версии шины

Хотя библиотека предлагает множество преимуществ, есть и некоторые ограничения, которые следует учитывать:

  1. Не поддерживаются методы, созданные через symbol: Это может влиять на некоторые особенности и паттерны проектирования, которые вы можете использовать в своем коде.

  2. Транспортный уровень использует копирование сообщения при передаче данных между потоками: Это может увеличить время выполнения кода, в зависимости от объема передаваемых данных. Необходимо учитывать это при работе с большими объемами данных.

  3. Работает исключительно с асинхронным API: Все взаимодействия с библиотекой должны быть асинхронными. Это не должно быть большой проблемой для современных веб-приложений, но все же стоит иметь в виду.

  4. Можно использовать в рамках одного сервиса только Promise или только RxJs Observable объектами: Это ограничивает гибкость в выборе подходов в разных частях одного и того же сервиса, но обеспечивает последовательность и согласованность в использовании.

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

Заключение

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

Главные преимущества этого подхода включают в себя:

  • Улучшение производительности путем распределения нагрузки на разные потоки.

  • Возможность создания более чистого и модульного кода.

  • Повышение масштабируемости и гибкости в разработке.

Однако также существуют и определенные ограничения, которые следует учитывать при работе с библиотекой.

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

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

  • Ссылка на код примеров для React, Angular, и Vue.js

  • Ссылка на GitHub репозиторий библиотеки

  • Ссылка на npm пакет

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

Если вы заинтересованы в этом проекте, ваш вклад будет очень ценен. Вы можете помочь улучшить библиотеку, исправить ошибки, добавить новые функции или даже написать документацию. Любые предложения и пул-реквесты приветствуются! :)

А если у вас возникнут вопросы или что-то окажется непонятным, не стесняйтесь обращаться. Может быть, есть какой-то пункт, который хотели бы разобрать подробнее? Пишите в комментариях. Ваше мнение и интерес к деталям очень важны для меня, и я постараюсь предоставить всю необходимую информацию.

Спасибо за внимание к статье!

Источник: https://habr.com/ru/articles/752526/


Интересные статьи

Интересные статьи

В данной статье хочу немного рассказать о классе Reflector, который был написан для облегчения работы с объектами и методами предоставляемыми.NET Reflection API из пространств...
Оригинал: https://klarasystems.com/articles/exploring-swap-on-freebsd/ Свободная память = память, потраченная впустую? Как использовать swap наилучшим образом.Для современных Unix-систем, таких к...
Переезд сродни пожару. Накал страстей нужно умножить на 10, когда речь идет о перевозке целого ЦОДа крупного банка. Сомневаетесь, что за 24 часа можно перевести 25 стоек,...
Месяц назад мы выпустили большое исследование зарплат айтишников. Сегодня сравним данные из него (реальные зарплаты, которые люди получают прямо сейчас) с данными из вакансий (с зарп...
Сейчас, на рубеже десятилетий, самое время критически переоценить то, что считалось правильным в недалёком прошлом, и выяснить, не потеряло ли оно актуальности в наши дни. Иногда вчерашние передо...