Что такое Web Workers в JavaScript?

SeniorJavaScript · Frontend·Обновлено 18 июля 2026
Коротко
Web Workers — это механизм браузера, позволяющий выполнять JavaScript-код в фоновых потоках, параллельно с основным потоком UI, без блокировки интерфейса. Взаимодействие с воркером происходит через асинхронный обмен сообщениями.

Web Workers в JavaScript

JavaScript в браузере является однопоточным: весь код, работа с DOM, обработка событий и рендеринг выполняются в одном главном потоке. При длительных вычислениях главный поток блокируется, интерфейс перестаёт реагировать на действия пользователя.

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

Типы Web Workers

  • Dedicated Worker — создаётся одной страницей и доступен только ей. Самый распространённый тип.
  • Shared Worker — может быть разделён между несколькими вкладками, фреймами и воркерами одного источника.
  • Service Worker — особый тип, работающий как прокси между браузером и сетью; используется для офлайн-кэширования и push-уведомлений.

Ключевые ограничения

Web Workers не имеют доступа к:

  • объекту window и DOM
  • document, parent
  • некоторым Web API (например, localStorage напрямую)

Воркеры имеют доступ к navigator, location, XMLHttpRequest, fetch, IndexedDB, setTimeout/setInterval, WebSocket, importScripts().

Модель обмена сообщениями

Связь между главным потоком и воркером осуществляется через postMessage / onmessage. Данные передаются по значению через структурированное клонирование (structured clone algorithm), а не по ссылке.

Для передачи больших объёмов данных (например, ArrayBuffer) без копирования используется механизм Transferable Objects — объект передаётся с передачей владения, и исходный контекст теряет к нему доступ.

Жизненный цикл воркера

  1. Главный поток создаёт воркер через new Worker(url)
  2. Браузер загружает скрипт воркера в отдельном потоке
  3. Обмен сообщениями через postMessage / onmessage
  4. Воркер завершается через worker.terminate() (из главного потока) или self.close() (изнутри воркера)

Практические сценарии применения

  • Парсинг и обработка больших JSON-файлов
  • Криптографические операции
  • Сжатие/распаковка данных
  • Обработка изображений (фильтры, трансформации пикселей)
  • Сложные математические вычисления (физические симуляции, ML-инференс)
  • Обработка данных в реальном времени (audio, video)

Inline Workers

Воркер можно создать без отдельного файла, используя Blob и URL.createObjectURL, что удобно при бандлинге.

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

Производительность и подводные камни

  • Создание воркера имеет overhead — не стоит создавать их на каждый запрос; используйте пул воркеров.
  • Структурированное клонирование больших объектов может быть дороже, чем само вычисление.
  • Ошибки в воркере не всплывают в главный поток автоматически — нужно обрабатывать событие onerror.
  • В Node.js аналогом являются worker_threads из модуля node:worker_threads.

Что хочет услышать интервьюер

Понимание однопоточной природы JavaScript и проблемы блокировки главного потока

Знание типов воркеров (Dedicated, Shared, Service) и их различий

Понимание модели обмена сообщениями: postMessage, structured clone, Transferable Objects

Осознание ограничений воркеров (нет доступа к DOM, window) и какие API доступны

Практический опыт: реальные сценарии применения, пулы воркеров, обработка ошибок

Пример: Dedicated Worker: тяжёлые вычисления без блокировки UI

// worker.ts
self.onmessage = (event: MessageEvent<number>) => {
  const n = event.data;
  // Тяжёлое вычисление (например, факториал через BigInt)
  let result = 1n;
  for (let i = 2n; i <= BigInt(n); i++) {
    result *= i;
  }
  self.postMessage(result.toString());
};

// main.ts
const worker = new Worker(new URL('./worker.ts', import.meta.url), {
  type: 'module',
});

worker.postMessage(50000); // не блокирует UI

worker.onmessage = (event: MessageEvent<string>) => {
  console.log('Результат:', event.data);
};

worker.onerror = (error) => {
  console.error('Ошибка в воркере:', error.message);
};

Пример: Transferable Objects: передача ArrayBuffer без копирования

// main.ts — передаём большой буфер с передачей владения
const buffer = new ArrayBuffer(1024 * 1024 * 64); // 64 МБ
const worker = new Worker('./image-worker.js');

// Третий аргумент — список transferable: буфер не копируется
worker.postMessage({ buffer }, [buffer]);

// После передачи buffer.byteLength === 0 — владение передано воркеру
console.log(buffer.byteLength); // 0

// image-worker.js
self.onmessage = (event) => {
  const { buffer } = event.data;
  const view = new Uint8Array(buffer);
  // Обрабатываем пиксели изображения
  for (let i = 0; i < view.length; i++) {
    view[i] = 255 - view[i]; // инверсия
  }
  // Возвращаем обратно с передачей владения
  self.postMessage({ buffer }, [buffer]);
};

Пример: Пул воркеров для параллельной обработки задач

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

  constructor(private readonly size: number, scriptUrl: string) {
    this.workers = Array.from({ length: size }, () => new Worker(scriptUrl));
    this.idle = [...this.workers];

    this.workers.forEach((worker) => {
      worker.onmessage = (e) => {
        // Воркер освободился — берём следующую задачу
        this.idle.push(worker);
        this.processQueue();
        // Найти резолвер для этого воркера (упрощённо)
      };
    });
  }

  run(data: unknown): Promise<unknown> {
    return new Promise((resolve, reject) => {
      this.queue.push({ resolve, reject, data });
      this.processQueue();
    });
  }

  private processQueue() {
    if (this.queue.length === 0 || this.idle.length === 0) return;
    const worker = this.idle.pop()!;
    const task = this.queue.shift()!;
    worker.onmessage = (e) => {
      task.resolve(e.data);
      this.idle.push(worker);
      this.processQueue();
    };
    worker.onerror = (e) => task.reject(e);
    worker.postMessage(task.data);
  }

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

// Использование
const pool = new WorkerPool(4, './compute-worker.js');
const results = await Promise.all(
  [1000, 2000, 3000, 4000].map((n) => pool.run(n))
);
pool.terminate();

Типичные ошибки

Путают Web Workers с асинхронностью (Promise, async/await) — воркеры дают настоящий параллелизм, а не конкурентность в одном потоке

Не учитывают стоимость сериализации при передаче данных и не используют Transferable Objects для больших буферов

Создают новый воркер на каждый запрос вместо переиспользования или пула воркеров

Забывают обрабатывать событие onerror — ошибки в воркере незаметно теряются

Пытаются обращаться к DOM или localStorage из воркера, не зная о его ограничениях

Лучшие курсы по теме

изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Feature-Sliced Design

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.5
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее