← Назад к вопросам

Как сделать JS многопоточным?

2.3 Middle🔥 151 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Многопоточность в JavaScript: Web Workers

JavaScript исторически был однопоточным, но современный стандарт предоставляет способ достичь параллелизма через Web Workers. Это механизм, который позволяет выполнять код в отдельных потоках без блокирования главного потока UI.

Проблема: однопоточность JavaScript

JavaScript работает в одном потоке — Event Loop. Если выполняется дорогая операция, UI зависает:

// Плохо: блокирует UI
function expensiveCalculation() {
  let result = 0;
  for (let i = 0; i < 1000000000; i++) {
    result += i;
  }
  return result;
}

button.addEventListener('click', () => {
  const result = expensiveCalculation(); // UI зависает на несколько секунд!
  console.log(result);
});

Решение 1: Web Workers для параллельного выполнения

Web Workers позволяют выполнять код в отдельном потоке:

worker.js (отдельный файл)

// Этот код выполняется в отдельном потоке
self.addEventListener('message', (event) => {
  const { data } = event;

  if (data.type === 'calculate') {
    // Дорогая операция в отдельном потоке
    let result = 0;
    for (let i = 0; i < data.count; i++) {
      result += i;
    }

    // Отправить результат обратно в главный поток
    self.postMessage({ result });
  }
});

main.js (главный поток)

// Создать worker
const worker = new Worker('worker.js');

button.addEventListener('click', () => {
  // Отправить задачу в worker
  worker.postMessage({ type: 'calculate', count: 1000000000 });

  console.log('Расчет начался, UI не зависает!');
});

// Слушать результат из worker
worker.addEventListener('message', (event) => {
  const { result } = event.data;
  console.log('Результат:', result);
});

Важные свойства Web Workers

1. Web Workers выполняются в отдельном потоке

// worker.js
console.log('Я в worker!');

// Нет доступа к:
// - DOM элементам
// - window объекту (есть self вместо этого)
// - localStorage (не рекомендуется)
// - родительскому документу

// Есть доступ к:
// - navigator, location
// - XMLHttpRequest, fetch
// - setTimeout, setInterval
// - обработчикам сообщений (postMessage)

2. Обмен данными через postMessage

// main.js -> worker.js
const worker = new Worker('worker.js');

// Отправить сообщение
worker.postMessage({
  type: 'process',
  data: [1, 2, 3, 4, 5],
  timestamp: Date.now()
});

// worker.js получит сообщение

3. Worker может отправить сообщение обратно

// worker.js
self.addEventListener('message', (event) => {
  const { data } = event;
  
  const result = data.reduce((a, b) => a + b);
  
  // Отправить результат
  self.postMessage({ success: true, result });
});

Практический пример: обработка изображения

// image-worker.js
self.addEventListener('message', (event) => {
  const { imageData } = event.data;

  // Обработка изображения в отдельном потоке
  const pixels = imageData.data;
  for (let i = 0; i < pixels.length; i += 4) {
    // Преобразование в оттенки серого
    const red = pixels[i];
    const green = pixels[i + 1];
    const blue = pixels[i + 2];
    const gray = (red + green + blue) / 3;

    pixels[i] = gray;
    pixels[i + 1] = gray;
    pixels[i + 2] = gray;
  }

  // Отправить обработанное изображение
  self.postMessage({ imageData });
});

// main.js
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

const worker = new Worker('image-worker.js');
worker.postMessage({ imageData });

worker.addEventListener('message', (event) => {
  const { imageData } = event.data;
  ctx.putImageData(imageData, 0, 0);
  console.log('Изображение обработано!');
});

Способ 2: Inline Workers (не всегда работают)

// Создать worker из функции (экспериментальное)
const code = `
  self.addEventListener('message', (event) => {
    const result = event.data * 2;
    self.postMessage(result);
  });
`;

const blob = new Blob([code], { type: 'application/javascript' });
const workerUrl = URL.createObjectURL(blob);
const worker = new Worker(workerUrl);

worker.postMessage(5);
worker.addEventListener('message', (e) => {
  console.log(e.data); // 10
});

Способ 3: Shared Workers (несколько вкладок)

// shared-worker.js
const connections = [];

self.addEventListener('connect', (event) => {
  const port = event.ports[0];
  connections.push(port);

  port.addEventListener('message', (event) => {
    // Отправить сообщение всем подключенным вкладкам
    connections.forEach(p => {
      p.postMessage(event.data);
    });
  });

  port.start();
});

// main.js
const worker = new SharedWorker('shared-worker.js');

worker.port.addEventListener('message', (event) => {
  console.log('От другой вкладки:', event.data);
});

worker.port.postMessage({ message: 'Привет из этой вкладки!' });
worker.port.start();

Ограничения Web Workers

// Web Workers НЕ имеют доступа к:
// - DOM (нельзя обновлять UI прямо)
// - window объекту
// - document объекту
// - parent объекту

// Решение: отправлять результаты в главный поток
// и обновлять UI оттуда

Реальный пример: обработка больших данных

// data-processor.js
self.addEventListener('message', (event) => {
  const { data, operation } = event.data;

  if (operation === 'filter') {
    const filtered = data.filter(item => item.value > 100);
    self.postMessage({ type: 'filter', result: filtered });
  } else if (operation === 'map') {
    const mapped = data.map(item => ({ ...item, doubled: item.value * 2 }));
    self.postMessage({ type: 'map', result: mapped });
  } else if (operation === 'sort') {
    const sorted = data.sort((a, b) => a.value - b.value);
    self.postMessage({ type: 'sort', result: sorted });
  }
});

// main.js
const worker = new Worker('data-processor.js');
const largeDataset = Array.from({ length: 1000000 }, (_, i) => ({
  id: i,
  value: Math.random() * 1000
}));

// Обработка без блокировки UI
worker.postMessage({ data: largeDataset, operation: 'filter' });

worker.addEventListener('message', (event) => {
  const { type, result } = event.data;
  console.log(`${type} завершена. Результатов: ${result.length}`);
  displayResults(result);
});

Когда использовать Web Workers

  1. Сложные вычисления (факториал, фибоначчи, криптография)
  2. Обработка больших данных (сортировка, фильтрация массивов)
  3. Обработка изображений/видео (фильтры, преобразования)
  4. Синтаксический анализ (парсинг JSON, XML)
  5. Криптографические операции

Когда НЕ использовать:

  • Если операция быстрая (<1ms)
  • Если нужен прямой доступ к DOM
  • Если много мала данных для обмена

Шпаргалка: Web Workers vs главный поток

ПараметрГлавный потокWeb Worker
Доступ к DOMДаНет
Доступ к windowДаНет (только self)
Скорость запускаСразуЗадержка (создание потока)
Общая памятьДаНет (копируется данные)
Идеален дляUI логикаДорогие вычисления

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

Как сделать JS многопоточным? | PrepBro