Реализация Web Workers для фоновых вычислений на сайте

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация Web Workers для фоновых вычислений на сайте
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация Web Workers для фоновых вычислений на сайте

JavaScript — однопоточный. Тяжёлая операция в main thread замораживает UI: скролл дёргается, анимации прерываются, кнопки не реагируют. Web Workers решают это, перенося вычисления в отдельный поток операционной системы.

Worker не имеет доступа к DOM, window, document. Общение с main thread — только через сообщения (postMessage/onmessage). Это ограничение — одновременно и защита от race conditions.

Базовая структура

Два файла: основной поток и Worker:

// worker.ts
self.onmessage = (event: MessageEvent) => {
  const { type, payload } = event.data

  switch (type) {
    case 'PROCESS': {
      const result = heavyComputation(payload)
      self.postMessage({ type: 'RESULT', payload: result })
      break
    }
  }
}

function heavyComputation(data: number[]): number {
  // Вычисление, которое заняло бы 500ms в main thread
  return data.reduce((sum, n) => sum + Math.sqrt(n), 0)
}
// main.ts
const worker = new Worker(new URL('./worker.ts', import.meta.url), {
  type: 'module',
})

worker.postMessage({ type: 'PROCESS', payload: largeArray })

worker.onmessage = (event: MessageEvent) => {
  const { type, payload } = event.data
  if (type === 'RESULT') {
    console.log('Результат:', payload)
  }
}

worker.onerror = (error) => {
  console.error('Worker error:', error.message)
}

// Завершить Worker
worker.terminate()

Типизированная обёртка

Работать с сырым postMessage неудобно. Типизированная обёртка решает это:

// worker-bridge.ts
type WorkerMessage<T extends Record<string, unknown>> = {
  [K in keyof T]: { type: K; payload: T[K] }
}[keyof T]

interface WorkerRequest {
  SORT: { array: number[]; direction: 'asc' | 'desc' }
  FILTER: { data: Record<string, unknown>[]; query: string }
  PARSE_CSV: { content: string }
}

interface WorkerResponse {
  SORT_DONE: number[]
  FILTER_DONE: Record<string, unknown>[]
  PARSE_CSV_DONE: Record<string, string>[]
  ERROR: { message: string }
}

class TypedWorker {
  private worker: Worker
  private pending = new Map<string, { resolve: Function; reject: Function }>()
  private seq = 0

  constructor(workerUrl: URL) {
    this.worker = new Worker(workerUrl, { type: 'module' })
    this.worker.onmessage = ({ data }) => {
      const { id, type, payload } = data
      const handler = this.pending.get(id)
      if (!handler) return

      this.pending.delete(id)
      if (type === 'ERROR') {
        handler.reject(new Error(payload.message))
      } else {
        handler.resolve(payload)
      }
    }
  }

  send<K extends keyof WorkerRequest>(
    type: K,
    payload: WorkerRequest[K]
  ): Promise<WorkerResponse[`${K}_DONE` & keyof WorkerResponse]> {
    return new Promise((resolve, reject) => {
      const id = String(++this.seq)
      this.pending.set(id, { resolve, reject })
      this.worker.postMessage({ id, type, payload })
    })
  }

  terminate(): void {
    this.worker.terminate()
  }
}

Передача больших данных — Transferable Objects

postMessage копирует данные по умолчанию. Для больших ArrayBuffer это дорого. Transferable Objects передаются по ссылке (owner transfer), без копирования:

// Создаём буфер
const buffer = new ArrayBuffer(1024 * 1024 * 10) // 10 MB
const view = new Float32Array(buffer)
// ... заполняем данными

// Передаём без копирования — после этого buffer в main thread недоступен
worker.postMessage({ type: 'PROCESS', payload: buffer }, [buffer])

// В Worker
self.onmessage = (event: MessageEvent) => {
  const buffer = event.data.payload as ArrayBuffer
  const view = new Float32Array(buffer)
  // обрабатываем...

  // Возвращаем обратно
  self.postMessage({ type: 'DONE', payload: buffer }, [buffer])
}

Transferable: ArrayBuffer, MessagePort, ImageBitmap, OffscreenCanvas, ReadableStream, WritableStream.

OffscreenCanvas — рендеринг в Worker

// main.ts
const canvas = document.getElementById('chart') as HTMLCanvasElement
const offscreen = canvas.transferControlToOffscreen()

worker.postMessage({ type: 'INIT_CANVAS', canvas: offscreen }, [offscreen])
worker.postMessage({ type: 'RENDER', data: chartData })
// chart-worker.ts
let ctx: OffscreenCanvasRenderingContext2D

self.onmessage = (event: MessageEvent) => {
  const { type, canvas, data } = event.data

  if (type === 'INIT_CANVAS') {
    ctx = canvas.getContext('2d')!
    return
  }

  if (type === 'RENDER') {
    renderChart(ctx, data)
  }
}

Пул Worker-ов

Для параллельной обработки нескольких задач:

class WorkerPool {
  private workers: Worker[] = []
  private queue: Array<{ resolve: Function; reject: Function; message: unknown }> = []
  private idle: Worker[] = []

  constructor(workerUrl: URL, poolSize = navigator.hardwareConcurrency || 4) {
    for (let i = 0; i < poolSize; i++) {
      const worker = new Worker(workerUrl, { type: 'module' })
      worker.onmessage = (event) => this.onWorkerMessage(worker, event)
      worker.onerror = (error) => this.onWorkerError(worker, error)
      this.workers.push(worker)
      this.idle.push(worker)
    }
  }

  execute(message: unknown): Promise<unknown> {
    return new Promise((resolve, reject) => {
      const task = { resolve, reject, message }
      const worker = this.idle.pop()
      if (worker) {
        this.dispatch(worker, task)
      } else {
        this.queue.push(task)
      }
    })
  }

  private dispatch(worker: Worker, task: { resolve: Function; reject: Function; message: unknown }): void {
    (worker as any).__resolve = task.resolve;
    (worker as any).__reject = task.reject;
    worker.postMessage(task.message)
  }

  private onWorkerMessage(worker: Worker, event: MessageEvent): void {
    (worker as any).__resolve?.(event.data)
    this.scheduleNext(worker)
  }

  private onWorkerError(worker: Worker, error: ErrorEvent): void {
    (worker as any).__reject?.(new Error(error.message))
    this.scheduleNext(worker)
  }

  private scheduleNext(worker: Worker): void {
    const next = this.queue.shift()
    if (next) {
      this.dispatch(worker, next)
    } else {
      this.idle.push(worker)
    }
  }

  terminate(): void {
    this.workers.forEach((w) => w.terminate())
  }
}

React-хук

function useWorker<TInput, TOutput>(workerUrl: URL) {
  const workerRef = useRef<Worker>()
  const [result, setResult] = useState<TOutput | null>(null)
  const [error, setError] = useState<string | null>(null)
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    workerRef.current = new Worker(workerUrl, { type: 'module' })

    workerRef.current.onmessage = ({ data }) => {
      setResult(data)
      setLoading(false)
    }
    workerRef.current.onerror = (e) => {
      setError(e.message)
      setLoading(false)
    }

    return () => workerRef.current?.terminate()
  }, [workerUrl.href])

  const run = useCallback((payload: TInput) => {
    setLoading(true)
    setError(null)
    workerRef.current?.postMessage(payload)
  }, [])

  return { run, result, error, loading }
}

Типичные задачи для Workers

  • Парсинг и трансформация больших CSV/JSON (>1MB)
  • Шифрование/дешифрование данных
  • Рендеринг Canvas-графики и диаграмм
  • Обработка изображений (ресайз, фильтры, конвертация)
  • Алгоритмы поиска и сортировки на больших массивах
  • Компрессия данных (pako, zlib)
  • Вычисление хешей (SHA-256, MD5)
  • Raytracing, физические симуляции

Срок: 1–2 дня в зависимости от сложности задач и необходимости пула Worker-ов.