Learning Book

Web Workers

Что такое Web Worker

Web Worker — отдельный поток выполнения JavaScript в браузере. Код в воркере работает параллельно с main thread — не блокирует UI, не мешает рендерингу.

// main.js — главный поток
const worker = new Worker('worker.js')

worker.postMessage({ n: 45 })         // отправляем задачу

worker.onmessage = (event) => {
  console.log('Результат:', event.data) // получаем ответ
}
// worker.js — отдельный поток
self.onmessage = (event) => {
  const result = fibonacci(event.data.n)
  self.postMessage(result)             // отправляем обратно
}

function fibonacci(n) {
  if (n <= 1) return n
  return fibonacci(n - 1) + fibonacci(n - 2)
}

Два потока, два файла, общение через сообщения. Main thread свободен — UI не тормозит.

Создание воркера

Из файла

const worker = new Worker('worker.js')
// или с type: 'module' для ES-модулей
const worker = new Worker('worker.js', { type: 'module' })

Inline Worker (через Blob)

Когда не хочется создавать отдельный файл:

const code = `
  self.onmessage = (e) => {
    const result = e.data * 2
    self.postMessage(result)
  }
`
const blob = new Blob([code], { type: 'application/javascript' })
const worker = new Worker(URL.createObjectURL(blob))

Inline-воркеры удобны для небольших задач и тестов.

Обмен сообщениями: postMessage

Единственный способ общения между main thread и worker — postMessage/onmessage:

// Main → Worker
worker.postMessage({ type: 'compute', data: [1, 2, 3] })

// Worker → Main
// внутри worker.js:
self.postMessage({ type: 'result', value: 6 })

Данные копируются при передаче — это называется structured cloning. Оригинал и копия полностью независимы.

Structured Clone Algorithm

Алгоритм клонирования определяет, что можно передать через postMessage:

МожноНельзя
Примитивы (string, number, boolean)Функции
Объекты и массивыDOM-элементы
Map, SetКлассы (теряют прототип)
Date, RegExpSymbol
ArrayBuffer, TypedArrayWeakMap, WeakRef
Blob, File, ImageDataЗамыкания
// ✅ Работает
worker.postMessage({ users: [{ name: 'Анна', age: 25 }] })
worker.postMessage(new Map([['key', 'value']]))

// ❌ Ошибка: функции нельзя клонировать
worker.postMessage({ callback: () => {} })
// DataCloneError: () => {} could not be cloned

Замыкания из главы 1 не передать в воркер — функции нельзя клонировать. Это фундаментальное ограничение: воркер не имеет доступа к scope основного потока.

Transferable Objects

Клонирование больших данных дорого. Transferable позволяет передать владение без копирования:

const buffer = new ArrayBuffer(1024 * 1024 * 100) // 100 MB

// ❌ Медленно: копирование 100 MB
worker.postMessage(buffer)

// ✅ Мгновенно: передача владения (zero-copy)
worker.postMessage(buffer, [buffer])
console.log(buffer.byteLength) // 0 — буфер больше не доступен в main thread

После transfer буфер нейтрализуется в отправляющем потоке — его размер становится 0. Данные теперь принадлежат воркеру.

Transferable-типы: ArrayBuffer, MessagePort, OffscreenCanvas, ImageBitmap, ReadableStream, WritableStream.

Ограничения воркеров

Воркер — изолированный контекст. Нет доступа к:

НедоступноПочему
document, DOMDOM не thread-safe
windowЕсть self вместо window
localStorageСинхронный API, не thread-safe
alert(), confirm()Привязаны к UI
Основной scopeПолная изоляция памяти

Доступно в воркере:

ДоступноПример
fetchСетевые запросы
setTimeout/setIntervalТаймеры
indexedDBАсинхронная БД
WebSocketРеал-тайм соединения
cryptoWeb Crypto API
importScripts()Загрузка скриптов
navigatorИнформация о браузере

Типы воркеров

ТипScopeКоличествоНазначение
Dedicated WorkerОдин tab1:1 со страницейCPU-bound задачи
Shared WorkerНесколько tabs1:NОбщее состояние между вкладками
Service WorkerOrigin1 на originКеширование, offline, push-уведомления

В этой главе мы работаем с Dedicated Worker — самый простой и частый тип.

Завершение воркера

// Из main thread — немедленное убийство
worker.terminate()

// Из самого воркера — корректное завершение
self.close()

terminate() убивает поток мгновенно — текущие операции прерываются. Используй, когда результат больше не нужен (пользователь ушёл со страницы, таймаут).

Обработка ошибок

worker.onerror = (event) => {
  console.error('Ошибка в воркере:', event.message)
  console.error('Файл:', event.filename, 'строка:', event.lineno)
}

// Или через addEventListener
worker.addEventListener('error', (event) => {
  event.preventDefault() // предотвращает вывод в консоль
  handleWorkerError(event)
})

Необработанная ошибка в воркере не крашит main thread — но воркер может перестать отвечать на сообщения.

Паттерн: запрос-ответ

postMessage — fire-and-forget. Чтобы связать запрос с ответом, добавляй идентификатор:

// main.js
let requestId = 0
const pending = new Map()

function compute(data) {
  const id = requestId++
  return new Promise((resolve, reject) => {
    pending.set(id, { resolve, reject })
    worker.postMessage({ id, data })
  })
}

worker.onmessage = (event) => {
  const { id, result, error } = event.data
  const handler = pending.get(id)
  if (handler) {
    pending.delete(id)
    error ? handler.reject(new Error(error)) : handler.resolve(result)
  }
}

// Использование
const result = await compute({ n: 42 })

Этот паттерн настолько частый, что библиотека Comlink делает его автоматически (подробнее в подглаве 5).

Итого

ФактОписание
Web WorkerОтдельный поток JS в браузере, не блокирует UI
postMessageЕдинственный канал связи, данные клонируются
Structured CloneКопирует объекты, массивы, Map/Set; не копирует функции и DOM
TransferableZero-copy передача ArrayBuffer — мгновенно, но источник теряет доступ
ОграниченияНет DOM, нет window, нет localStorage
terminate()Мгновенное убийство воркера из main thread