JavaScript является однопоточным, он синхронно выполняет одну строку кода за раз. Нам нужно выполнять интенсивные задачи, и мы хотим, чтобы пользовательский интерфейс работал плавно со скоростью 60 кадров в секунду. Приложение получает 16 миллисекунд для запуска кода JavaScript и повторного рендеринга DOM. Таким образом, если мы запустим задачу с интенсивным использованием ЦП, тогда поток пользовательского интерфейса будет занят, и ваше приложение в это время перестанет отвечать. Мы можем решить эту проблему с помощью Web Worker.

Что такое Web Worker?

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

Работать с Web Worker очень просто. Во-первых, вам нужно создать файл JavaScript, содержащий вашу часть приложения, которую вы хотите, чтобы ваш веб-воркер выполнял. Браузеры предоставляют Worker API для создания экземпляра объекта Worker, передавая путь к рабочему файлу.

const webWorker = new Worker(‘worker.js’)

После создания экземпляра воркера для связи с воркером мы используем postMessage () для отправки данных в рабочий поток.

webWorker.postMessage(someData)

Чтобы получать данные из основного потока в worker, мы объявляем функцию onmessage или добавляем прослушиватель событий.

// worker.js
self.onmessage = function(event) {
  console.log(`Message from main thread ${event.data}`);
   // event.data = someData
}
// or
self.addEventListener("message", (event) => {
 console.log(`Message from main thread ${event.data}`);
}

Аналогично для отправки данных из рабочего в основной поток мы используем self.postMessage (), а для получения данных в основном потоке мы объявляем onmessage или добавляем прослушиватель событий.

// worker.js
self.postMessage(someOtherData)
// main.js
webWorker.addEventListener("message" , (event) => {
 console.log(`Message from worker ${event.data}`);
  // event.data = someOtherData
}

Как только вы закончите с рабочим, вы можете завершить его, вызвав функцию terminate () для вашего рабочего объекта, или вы также можете завершить рабочий процесс изнутри рабочего, используя self.close ()

// worker.js
self.close();
// main.js
webWorker.terminate();

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

// main.js
webWorker.addEventListener("error", (error)=> {
   console.log('error in web worker-> ' , error.message);
});

Мы также можем импортировать внешние скрипты внутри веб-воркера, используя глобальную функцию importScripts (), она принимает ноль или более URI в качестве параметров.

// worker.js
importScripts('imageProcessing.js') // single import
importScripts('imageProcessing.js', 'enc-aes.js') // multiple import

Просто и легко, давайте рассмотрим простой пример и сравним производительность.

// main.js <without web worker>
function task(n) {
 for (let i = 1; i <= n; i++);
}
task(10000000000);

Для выполнения приведенного выше сценария на моем компьютере (Intel® Core ™ i7–8550U CPU @ 1,80 ГГц × 8) потребовалось около 16 секунд. Теперь проверьте тот же пример с веб-воркером.

// main.js
const worker = new Worker('worker.js');
worker.postMessage(1000000000);
worker.addEventListener('message', ({ data }) =>{
   worker.terminate();
});
// worker.js
function task(n) {
 for (let i = 1; i <= n; i++);
}
self.onmessage = function({ data }) {
  if (data) {
    task(parseInt(data));
    self.postMessage('');
  }
};

На выполнение той же задачи с веб-воркером потребовалось около 2 с.
Как я уже упоминал ранее, у веб-воркера высокие затраты на производительность при запуске, мы можем проверить это с помощью приведенного выше примера, уменьшив значение n. если мы сравним производительность времени с n = 100000, без веб-воркера это заняло около 3 мс, а с веб-воркером - около 52 мс. Как видите, использование веб-воркера для небольших задач не является оптимальным решением.

Зачем использовать Web Worker?

  • Повысьте производительность вашего приложения и улучшите взаимодействие с пользователем.
  • Логика приложения не блокирует поток пользовательского интерфейса.
  • Web Worker позволит вам использовать несколько ядер клиентской машины.
  • Поддерживается всеми современными браузерами (IE 10+).

Ограничение Web Worker?

  • Нет доступа к DOM API.
  • Он не разделяет память с потоком пользовательского интерфейса.
  • Это увеличивает использование ЦП и памяти клиентского компьютера.
  • Скрипт веб-воркера должен обслуживаться из того же домена и протокола.

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

  • Шифрование / дешифрование. Компания NoBroker очень серьезно относится к безопасности. Внутренние API в NoBroker зашифрованы и должны расшифровывать их во внешнем интерфейсе. В Dashboard у нас есть большой набор данных, поступающих из API, поэтому процесс дешифрования требует времени, что увеличивает время загрузки. Поэтому мы переносим процесс дешифрования на веб-воркер, поскольку DOM API не требуется. Поскольку мы знаем, что стоимость запуска веб-воркера высока, мы запускаем веб-воркер при загрузке панели управления и оставляем экземпляр веб-воркера доступным через приложение, поэтому нам не нужно тратить время каждый раз, когда мы хотим расшифровать данные.
  • Фоновый опрос: Web worker очень полезен для фонового опроса и обновляет данные. Мы можем обрабатывать новые данные в веб-воркере и отправлять обновление в наше приложение без дополнительной нагрузки на поток пользовательского интерфейса.
  • Обработка звука или изображения. Обработка изображения или звука требует интенсивной работы ЦП. Если мы попытаемся выполнить эти операции в основном потоке пользовательского интерфейса, ваше приложение перестанет отвечать. Всю вычислительную логику можно добавить к Web Worker, чтобы избежать блокировки потока пользовательского интерфейса. Вы можете разделить свои задачи на несколько рабочих и полностью использовать несколько ядер и увеличить скорость обработки данных.
  • Проверка орфографии. Для программы проверки орфографии у нас есть словарь, содержащий правильные слова, хранящиеся в структуре данных trie, чтобы сделать поиск эффективным. Когда ввод предоставляется программе, мы должны искать точное совпадение или совпадение, которое близко к этому слову. Всю эту обработку мы можем передать веб-воркерам, не загружая поток пользовательского интерфейса.
  • Предварительная выборка данных. Чтобы оптимизировать и повысить скорость работы вашего приложения, мы можем предварительно получить данные и сохранить их с помощью веб-воркера. Поэтому, когда позже нам понадобятся эти данные, чтобы мы могли получить эти данные из веб-воркера вместо вызова API, что сэкономит время загрузки. Например, в NoBroker, когда мы ищем недвижимость в определенной области, мы получаем подмножество всего результата. Чтобы сократить время загрузки при нажатии на следующую страницу, мы выполняем предварительную выборку.

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