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

Есть ли возможность запускать в браузере тяжелые операции в background?

1.0 Junior🔥 221 комментариев
#Браузер и сетевые технологии

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

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

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

Есть ли возможность запускать в браузере тяжелые операции в background?

Да, есть несколько способов! JavaScript выполняется в одном потоке (main thread), что блокирует UI при тяжелых вычислениях. Расскажу о решениях.

Проблема: блокировка main thread

// ПЛОХО: тяжелая операция блокирует UI
function heavyCalculation() {
  let sum = 0;
  for (let i = 0; i < 1000000000; i++) {
    sum += i;
  }
  return sum;
}

// При вызове UI замирает на несколько секунд
const result = heavyCalculation();

// Пользователь не может нажимать кнопки, скролить - всё заморожено

1. Web Workers (основной способ)

Web Workers позволяют запускать JavaScript в отдельном потоке.

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

// Создаём worker
const worker = new Worker('/workers/heavy.js');

// Отправляем данные в worker
worker.postMessage({
  type: 'CALCULATE',
  data: 1000000000
});

// Получаем результат когда он готов
worker.onmessage = (event) => {
  const { result } = event.data;
  console.log('Result:', result);
  document.getElementById('result').textContent = result;
};

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

Worker поток (workers/heavy.js):

// В worker есть доступ к:
// - navigator
// - setTimeout, setInterval
// - XMLHttpRequest, fetch
// - но НЕ к DOM!

self.onmessage = (event) => {
  const { type, data } = event.data;
  
  if (type === 'CALCULATE') {
    let sum = 0;
    // Тяжёлая операция не блокирует главный поток
    for (let i = 0; i < data; i++) {
      sum += i;
    }
    
    // Отправляем результат обратно
    self.postMessage({
      result: sum
    });
  }
};

Использование в React компоненте:

import { useState, useEffect, useRef } from 'react';

export function HeavyCalculation() {
  const [result, setResult] = useState<number | null>(null);
  const [loading, setLoading] = useState(false);
  const workerRef = useRef<Worker | null>(null);
  
  useEffect(() => {
    // Создаём worker один раз
    workerRef.current = new Worker(
      new URL('../workers/heavy.js', import.meta.url),
      { type: 'module' }
    );
    
    workerRef.current.onmessage = (event) => {
      setResult(event.data.result);
      setLoading(false);
    };
    
    return () => {
      workerRef.current?.terminate();
    };
  }, []);
  
  const handleCalculate = () => {
    if (!workerRef.current) return;
    setLoading(true);
    workerRef.current.postMessage({
      type: 'CALCULATE',
      data: 1000000000
    });
  };
  
  return (
    <div>
      <button onClick={handleCalculate} disabled={loading}>
        {loading ? 'Calculating...' : 'Calculate'}
      </button>
      {result !== null && <p>Result: {result}</p>}
    </div>
  );
}

Плюсы:

  • Настоящее параллелизм (отдельный поток)
  • UI остаётся отзывчивым
  • Можно передавать сложные данные

Минусы:

  • Нет доступа к DOM
  • Overhead на создание worker
  • Сложнее в отладке

2. requestIdleCallback

Запускает код когда браузер свободен (нет других операций).

function processLargeArray(array) {
  let index = 0;
  
  function processChunk() {
    // Обрабатываем небольшой кусок данных
    const endIndex = Math.min(index + 1000, array.length);
    
    while (index < endIndex) {
      // Обработка элемента
      const item = array[index];
      // Что-то делаем с item
      index++;
    }
    
    if (index < array.length) {
      // Запланируем обработку следующего куска когда браузер свободен
      requestIdleCallback(processChunk);
    } else {
      console.log('Обработка завершена');
    }
  }
  
  requestIdleCallback(processChunk);
}

Плюсы:

  • Простой API
  • Встроенный в браузер
  • Имеет доступ к DOM

Минусы:

  • Не настоящий параллелизм
  • Может быть медленно на перегруженных устройствах
  • Браузер может не вызвать callback надолго

3. setTimeout с разбиением на части (chunking)

Разбиваем большую операцию на части и запускаем их асинхронно.

async function processArray(items) {
  const chunkSize = 100;
  
  for (let i = 0; i < items.length; i += chunkSize) {
    const chunk = items.slice(i, i + chunkSize);
    
    // Обрабатываем кусок
    await new Promise(resolve => {
      setTimeout(() => {
        chunk.forEach(item => {
          // Обработка
        });
        resolve(undefined);
      }, 0);
    });
    
    // Обновляем прогресс если нужно
    updateProgress((i + chunkSize) / items.length);
  }
}

Плюсы:

  • Простая реализация
  • Работает везде

Минусы:

  • Всё ещё в main thread
  • Медленнее чем Web Workers

4. SharedArrayBuffer (продвинутый вариант)

Для обмена памятью между workers.

// Главный поток
const sharedBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 1000);
const sharedArray = new Int32Array(sharedBuffer);

const worker = new Worker('/worker.js');
worker.postMessage({ buffer: sharedBuffer });

// Worker может напрямую менять данные в sharedBuffer
// Главный поток видит изменения в реальном времени

ВАЖНО: SharedArrayBuffer требует определённых заголовков COOP/COEP.

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

// Worker для обработки CSV
// worker.ts
self.onmessage = async (event) => {
  const { csv } = event.data;
  
  // Парсим CSV в worker'е
  const lines = csv.split('\n');
  const data = lines.map(line => line.split(','));
  
  // Обработка
  const processed = data.map(row => ({
    name: row[0],
    value: parseInt(row[1]),
    date: new Date(row[2])
  }));
  
  self.postMessage({
    success: true,
    data: processed
  });
};

// Главный компонент
import { useCallback, useState } from 'react';

export function CsvUploader() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  
  const handleFileUpload = useCallback((file: File) => {
    setLoading(true);
    
    const reader = new FileReader();
    
    reader.onload = (event) => {
      const csv = event.target?.result as string;
      
      const worker = new Worker(
        new URL('../workers/csv.ts', import.meta.url),
        { type: 'module' }
      );
      
      worker.postMessage({ csv });
      
      worker.onmessage = (event) => {
        setData(event.data.data);
        setLoading(false);
        worker.terminate();
      };
    };
    
    reader.readAsText(file);
  }, []);
  
  return (
    <div>
      <input
        type="file"
        accept=".csv"
        onChange={(e) => e.target.files?.[0] && handleFileUpload(e.target.files[0])}
      />
      {loading && <p>Processing...</p>}
      {data && <p>Processed {data.length} rows</p>}
    </div>
  );
}

Сравнение подходов

СпособUI блокировкаДоступ к DOMСложностьСкорость
Web WorkerНетНетСредняяВысокая
requestIdleCallbackМинимумДаПростаяСредняя
setTimeout chunkingМинимумДаПростаяНизкая
SharedArrayBufferНетНетВысокаяОчень высокая

Рекомендации

Используй Web Workers когда:

  • Операция займёт > 100ms
  • Нужна максимальная производительность
  • Не нужен доступ к DOM
  • Примеры: обработка больших данных, криптография, обработка изображений

Используй requestIdleCallback когда:

  • Операция может выполняться когда браузер свободен
  • Нужен доступ к DOM
  • Примеры: аналитика, очистка кеша, фоновые операции

Используй setTimeout chunking когда:

  • Операция простая
  • Нужна кроссбраузерность
  • Производительность некритична

Итог

Да, в браузере есть несколько способов запускать тяжелые операции без блокировки UI:

  1. Web Workers - лучший способ для тяжёлых вычислений
  2. requestIdleCallback - для фоновых задач с доступом к DOM
  3. setTimeout chunking - для простых случаев
  4. SharedArrayBuffer - для экстремальной производительности

Выбор зависит от требований задачи и необходимости в доступе к DOM.

Есть ли возможность запускать в браузере тяжелые операции в background? | PrepBro