Что такое Web worker?
СкопированоWeb worker — это API, которое позволяет выполнять код вне основного потока. Благодаря этому долгие или сложные вычисления, которые выполняются на воркерах, не блокируют пользовательский интерфейс (UI).
Веб-воркер создаётся в основном потоке. При создании воркеру передаётся URL-адрес скрипта. После загрузки создаётся отдельный поток, в котором выполнится скрипт воркера.
У скрипта будет свой собственный контекст, отличный от контекста окна window. В основном потоке глобальный контекст привязывается к переменной window, а в воркере — к переменной self. Контекст выполнения веб-воркера WorkerGlobalScope отличается от контекста выполнения основного потока. У него нет доступа к объекту документа document и DOM API.
Особенности потока выполнения
СкопированоПоток, в котором выполняется код воркера, изолирован от основного. В Chromium каждому из этих потоков соответствует свой собственный экземпляр движка JavaScript. Из-за этого создание нового воркера считается «тяжёлой» операцией. Обычно предполагают, что воркеров будет немного, и они будут долго жить.
Потоки могут общаться между собой через отправку сообщений. Используйте для отправки сообщений функцию post.
Как создать и запустить?
СкопированоВсё просто: назовите конструктор Worker и передайте туда URL-адрес JavaScript-файла.
// window context app.jsconst worker = new Worker('worker.js')
// window context app.js
const worker = new Worker('worker.js')
Воркер использует механизм сообщений для общения с основным потоком. Для отправки сообщения используется метод post.
// Основной поток: app.jsconst worker = new Worker('worker.js')// Отправляем сообщение из основного потока в воркерworker.postMessage({ message: '415-ый, я база, ответьте' })
// Основной поток: app.js
const worker = new Worker('worker.js')
// Отправляем сообщение из основного потока в воркер
worker.postMessage({ message: '415-ый, я база, ответьте' })
В глобальном контексте воркера есть обработчик onmessage. Его можно использовать, чтобы принимать сообщения. Воркер также может отправлять сообщения в основной поток при помощи функции post. Функцию можно вызывать в любом месте воркера.
// Воркер: worker.jsonmessage = function (e) { // Слушаем сообщения из основного потока if (e.data.message === '415-ый, я база, ответьте') { // Отправляем сообщение из воркера в основной поток postMessage('База, это 415-ый, как слышно?') }}
// Воркер: worker.js
onmessage = function (e) { // Слушаем сообщения из основного потока
if (e.data.message === '415-ый, я база, ответьте') {
// Отправляем сообщение из воркера в основной поток
postMessage('База, это 415-ый, как слышно?')
}
}
Чтобы получать сообщения в основном потоке, используйте метод-обработчик onmessage объекта Worker.
// window context app.jsconst worker = new Worker('worker.js')worker.postMessage({ message: '415-ый, я база, ответьте' })worker.onmessage = function (e) { // Слушаем сообщения из воркера console.log(e.data) // База, это 415-ый, как слышно?}
// window context app.js
const worker = new Worker('worker.js')
worker.postMessage({ message: '415-ый, я база, ответьте' })
worker.onmessage = function (e) { // Слушаем сообщения из воркера
console.log(e.data)
// База, это 415-ый, как слышно?
}
Внимательный читатель заметит, что в воркер отправился объект со свойством message, а от воркера пришла строка. В функцию post можно передавать значения любого типа, включая объекты. Единственное ограничение — передаваемые данные должны поддерживать алгоритм структурированного клонирования.
Что доступно внутри?
СкопированоРанее упоминали, что в контексте выполнения воркера недоступны многие API из объекта window основного потока. Что же тогда доступно? Перечислим некоторые функции API, которые часто используются: fetch, set, set, request и queue. Для любознательных — полный список поддерживаемых API.
Типы воркеров
СкопированоВ примере выше рассмотрели первый тип воркеров — Dedicated Worker. Он будет доступен только в том потоке, который его создал. Это может быть основной поток или поток другого воркера. Но что, если мы хотим использовать воркер в разных вкладках браузера? Для этого используют другой типа воркера — Shared Worker.
Shared Workers позволяет создать поток, разделяемый между несколькими вкладками, iframe или окнами в пределах одного и того же происхождения (origin). Это означает, что Shared Worker может быть использован одновременно несколькими частями веб-приложения для обмена данными, синхронизации состояний или выполнения фоновых задач без необходимости повторной загрузки или дублирования в каждой вкладке или окне. Тут стоит отметить, что состояние Shared Worker будет живо, пока о нём кто-то помнит.
Разберёмся, как создать и запустить Shared Worker.
Логика схожа с логикой Dedicated Worker, но есть несколько исключений. Во-первых, для создания Shared нужно использовать конструктор Shared. Во-вторых, onmessage и post доступны в свойстве воркера port:
// Первая вкладка: app1.jsconst sharedWorker = new SharedWorker('worker.js')sharedWorker.port.onmessage = (event) => { console.log('data from worker', event)}const sendDataToWorker = () => { sharedWorker.port.postMessage(1)}
// Первая вкладка: app1.js
const sharedWorker = new SharedWorker('worker.js')
sharedWorker.port.onmessage = (event) => {
console.log('data from worker', event)
}
const sendDataToWorker = () => {
sharedWorker.port.postMessage(1)
}
То же самое делаем в другой вкладке:
// Вторая вкладка: app2.jsconst sharedWorker = new SharedWorker('worker.js')sharedWorker.port.onmessage = (event) => { console.log('data from worker', event)}const sendDataToWorker = () => { sharedWorker.port.postMessage(2)}
// Вторая вкладка: app2.js
const sharedWorker = new SharedWorker('worker.js')
sharedWorker.port.onmessage = (event) => {
console.log('data from worker', event)
}
const sendDataToWorker = () => {
sharedWorker.port.postMessage(2)
}
Код SharedWorker выглядит так:
// Воркер: worker.jslet sum = 0onconnect = (connect) => { const port = connect.ports[0] // В ports всегда один элемент port.onmessage = (event) => { sum += event } port.postMessage(sum)}
// Воркер: worker.js
let sum = 0
onconnect = (connect) => {
const port = connect.ports[0] // В ports всегда один элемент
port.onmessage = (event) => {
sum += event
}
port.postMessage(sum)
}
Обработчик события onconnect принимает event (мы называем его connect). Внутри обработчика используется свойство ports – массив в котором всегда будет один элемент. Используя свойство onmessage объекта port можно подписаться на сообщения из других потоков. Отправка сообщения также происходит через port.
Вложенность Web Workers
СкопированоВоркеры могут быть вложенными, и одни воркеры могут управлять другими воркерами. Работа с вложенными воркерами не отличается от работы с воркерами в основном потоке.
Импорты в Web workers
СкопированоНачиная с июня 2023 года, практически все браузеры поддерживают импорт ES-модулей в контексте воркеров. Поэтому можно использовать конструкцию import xxxxx from ‘lib’. Эта информация пригодится вам при настройке сборки приложения.
Отправка данных в Web worker
СкопированоДанные передаваемые в post по умолчанию копируются, что может быть медленно, особенно при передаче больших или сложных объектов.
Отправка данных без копирования
СкопированоДля оптимизации производительности и минимизации затрат на копирование данных можно использовать технику передачи данных через Transferable objects. Transferable objects не копируются, а перемещаются между контекстами. После оправки Transferable object пропадает из места откуда его отправили. Примерами transferable objects являются Array и Message.
Пример использования Transferable objects
СкопированоОтправка данных в веб-воркер:
// Создание ArrayBufferconst buffer = new ArrayBuffer(1024) // 1024 байта// Отправка ArrayBuffer в воркерworker.postMessage(buffer, [buffer])// теперь buffer не доступен в основном потоке
// Создание ArrayBuffer
const buffer = new ArrayBuffer(1024) // 1024 байта
// Отправка ArrayBuffer в воркер
worker.postMessage(buffer, [buffer])
// теперь buffer не доступен в основном потоке
Прием данных в воркере:
onmessage = function(e) { const buffer = e.data // Получение ArrayBuffer // Можно начать работу с данными};
onmessage = function(e) {
const buffer = e.data // Получение ArrayBuffer
// Можно начать работу с данными
};
В этом примере объект типа Array отправляется в воркер через post, массив с этим объектом также передаётся вторым аргументом как transferable object.
Обратите внимание:
- После передачи transferable object, источник теряет доступ к объекту. Это значит, что объект нельзя использовать в источнике после его отправки.
- Не все типы могут быть переданы как transferable object.
Array,Buffer Messageточно можно перемещать.Port
Transferable objects работают быстро, так как позволяют избегать глубокого копирования. Это особенно заметно при работе с большими или сложными объектами в приложениях, требующих высокой производительности. Например в играх, графических редакторах и обработчиках видео и аудио в реальном времени.
Заключение
СкопированоВоркеры — это мощный инструмент для разработки более отзывчивых и производительных веб-приложений. Они позволяют разгрузить основной поток от тяжелых вычислений, улучшают пользовательский опыт. Однако нужно учитывать их ограничения и особенности для эффективного использования в своих проектах.