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

Как сделать чтобы интерфейс не зависал при обработке большого массива?

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

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

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

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

Как сделать чтобы интерфейс не зависал при обработке большого массива?

Зависание интерфейса при обработке больших массивов данных — распространённая проблема в Web приложениях. Это происходит, когда JavaScript занимает весь основной поток (main thread), блокируя рендеринг и обработку событий. JavaScript — однопоточный язык, поэтому длительные операции замораживают весь интерфейс. Существует несколько проверенных методов решения этой проблемы.

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

Web Workers позволяют выполнять вычисления в фоновом потоке, не блокируя основной поток UI:

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

function processLargeArray(array) {
  worker.postMessage({
    type: 'PROCESS_ARRAY',
    data: array
  });
}

worker.onmessage = (event) => {
  const result = event.data;
  console.log('Result from worker:', result);
  updateUI(result);
};

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

// heavy-computation.js (в отдельном файле)
self.onmessage = (event) => {
  const array = event.data.data;
  
  // Тяжелые вычисления НЕ блокируют UI
  const result = array
    .map(item => expensiveOperation(item))
    .filter(item => item.isValid)
    .reduce((acc, item) => acc + item.value, 0);
  
  self.postMessage(result);
};

Преимущества:

  • Полностью отделяет вычисления от UI
  • Использует настоящий параллелизм (true parallelism)
  • Идеально для CPU-intensive операций

Недостатки:

  • Нельзя обращаться к DOM из worker
  • Overhead на создание и коммуникацию
  • Сложнее отладить

2. requestIdleCallback — выполнение в свободное время браузера

Этот API позволяет выполнять код только когда браузер не занят:

function processLargeArrayIdleCallback(array, callback) {
  let index = 0;
  
  function processChunk(deadline) {
    // Продолжаем обработку, пока есть время и работа
    while (index < array.length && deadline.timeRemaining() > 1) {
      callback(array[index]);
      index++;
    }
    
    // Если ещё есть элементы, запланируй следующий chunk
    if (index < array.length) {
      requestIdleCallback(processChunk);
    }
  }
  
  requestIdleCallback(processChunk);
}

// Использование:
processLargeArrayIdleCallback(data, (item) => {
  console.log('Processing:', item);
});

Преимущества:

  • Не блокирует UI
  • Браузер сам решает, когда выполнить код
  • Идеально для низкоприоритетных операций

Недостатки:

  • Может быть медленнее
  • Поддержка браузерами не полная
  • deadline может быть очень маленький

3. requestAnimationFrame для прогрессивной обработки

Основной поток освобождается между кадрами (60fps = 16.6ms на кадр):

function processArrayWithRAF(array, callback) {
  let index = 0;
  const frameTime = 16; // ms per frame at 60fps
  
  function processNextFrame() {
    const frameStart = performance.now();
    
    // Обрабатываем элементы в рамках одного кадра
    while (index < array.length) {
      const elapsed = performance.now() - frameStart;
      
      if (elapsed > frameTime) {
        break; // Закончилось время для этого кадра
      }
      
      callback(array[index]);
      index++;
    }
    
    // Есть ещё элементы?
    if (index < array.length) {
      requestAnimationFrame(processNextFrame);
    }
  }
  
  requestAnimationFrame(processNextFrame);
}

Преимущества:

  • Синхронизирован с refresh rate монитора
  • Сохраняет плавность анимаций
  • Простая реализация

Недостатки:

  • Зависит от FPS
  • Может быть медленнее чем Web Workers

4. Chunking с setTimeout — простой и надёжный способ

Разделяем массив на части и обрабатываем с задержкой:

async function processArrayInChunks(array, chunkSize = 1000) {
  for (let i = 0; i < array.length; i += chunkSize) {
    const chunk = array.slice(i, i + chunkSize);
    
    // Обрабатываем текущий chunk
    chunk.forEach(item => processItem(item));
    
    // Освобождаем главный поток
    await new Promise(resolve => setTimeout(resolve, 0));
  }
}

// Использование:
await processArrayInChunks(largeArray, 5000);
console.log('Done!');

Преимущества:

  • Очень простая реализация
  • Работает везде
  • Можно контролировать размер chunk'а

Недостатки:

  • Медленнее чем Web Workers
  • setTimeout может быть неточнее

5. Виртуализация для списков (React/Vue specific)

Для больших списков используй виртуализацию:

// React пример
import { FixedSizeList } from 'react-window';

function LargeList({ items }) {
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={35}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>
          {items[index].name}
        </div>
      )}
    </FixedSizeList>
  );
}

Преимущества:

  • Рендерит только видимые элементы
  • Огромный прирост производительности
  • Специально для списков

Сравнение методов

МетодСкоростьСложностьИспользование
Web WorkerБыстроСредняяCPU-интенсивные операции
requestIdleCallbackМедленноНизкаяНизкоприоритетные задачи
requestAnimationFrameБыстроНизкаяВизуальные обновления
ChunkingМедленноНизкаяПростые обработки
ВиртуализацияОчень быстроСредняяБольшие списки

Практические рекомендации

  1. Для числовых вычислений → Web Worker
  2. Для сортировки больших массивов → Web Worker
  3. Для отрисовки больших списков → Виртуализация
  4. Для простых операций → Chunking с setTimeout
  5. Для фоновых операций → requestIdleCallback

Выбери метод в зависимости от типа операции и требований производительности. Чаще всего оптимальное решение — комбинация нескольких подходов.