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

Как избежать блокировки Event Loop?

1.0 Junior🔥 81 комментариев
#JavaScript Core

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

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

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

Как избежать блокировки Event Loop

Event Loop - это сердце JavaScript. Если его заблокировать тяжелыми вычислениями, приложение зависнет, интерфейс перестанет реагировать на клики. Я расскажу, как это предотвратить.

Как работает Event Loop

JavaScript однопоточный, и всё выполняется в одном потоке:

// 1. Синхронный код выполняется СРАЗУ
console.log('1. Start');

// 2. setTimeout отправляет задачу в очередь (не выполняет сразу)
setTimeout(() => {
  console.log('2. Timeout');
}, 0);

// 3. Обещание (Promise) тоже в очередь
Promise.resolve().then(() => {
  console.log('3. Promise');
});

// 4. Синхронный код продолжает выполняться
console.log('4. End');

// Порядок вывода: 1, 4, 3, 2
// Почему 3 перед 2? Потому что Promises (микротаски) выполняются раньше setTimeout (макротаски)

Проблема 1: Блокирующие вычисления

Если сделать тяжелые вычисления в главном потоке, интерфейс зависнет:

// ПЛОХО - блокирует UI на несколько секунд
function calculateBad() {
  console.log('Calculating...');
  let sum = 0;
  for (let i = 0; i < 10_000_000_000; i++) {
    sum += i;
  }
  console.log('Done'); // Интерфейс зависнет!
  return sum;
}

button.addEventListener('click', calculateBad);

Решение 1: Разбить на части с setTimeout

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

// ХОРОШО - работает асинхронно
async function calculateGood() {
  let sum = 0;
  const chunk = 100_000_000; // Обработать за раз
  const total = 10_000_000_000;

  for (let i = 0; i < total; i += chunk) {
    // Обработать часть данных
    for (let j = i; j < i + chunk && j < total; j++) {
      sum += j;
    }
    // Дать Event Loop возможность обработать события
    await new Promise(resolve => setTimeout(resolve, 0));
  }

  console.log('Done');
  return sum;
}

button.addEventListener('click', calculateGood);

Решение 2: Web Workers для тяжелых вычислений

Для серьезных вычислений используйте отдельный поток:

// main.js
function calculateHeavy() {
  // Создаем worker (отдельный поток)
  const worker = new Worker('worker.js');

  // Отправляем данные в worker
  worker.postMessage({ number: 10_000_000_000 });

  // Получаем результат когда готово
  worker.onmessage = (event) => {
    console.log('Result:', event.data);
    // UI НИКОГДА не зависает!
  };

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

// worker.js (выполняется в отдельном потоке)
self.onmessage = (event) => {
  const { number } = event.data;
  let sum = 0;

  // Тяжелые вычисления БЕЗ блокирования главного потока
  for (let i = 0; i < number; i++) {
    sum += i;
  }

  // Отправляем результат обратно
  self.postMessage(sum);
};

Проблема 2: Бесконечные циклы в обработчиках

Это классическая ошибка:

// ПЛОХО - зависает
input.addEventListener('input', (e) => {
  // Бесконечный цикл - никогда не завершится
  while (true) {
    console.log(e.target.value);
  }
});

// ХОРОШО - управляемая обработка
input.addEventListener('input', (e) => {
  const value = e.target.value;
  // Обработать значение без циклов
  updateUI(value);
});

Решение 3: requestAnimationFrame для плавной анимации

Для работы с анимацией используйте requestAnimationFrame (синхронизируется с частотой обновления монитора):

// НЕПРАВИЛЬНО - может блокировать
function animateBad() {
  const element = document.getElementById('box');
  let position = 0;

  setInterval(() => {
    position += 10;
    element.style.left = position + 'px';
  }, 16); // ~60 FPS
}

// ПРАВИЛЬНО - синхронизируется с браузером
function animateGood() {
  const element = document.getElementById('box');
  let position = 0;

  function animate() {
    position += 10;
    if (position < 500) {
      element.style.left = position + 'px';
      requestAnimationFrame(animate);
    }
  }

  requestAnimationFrame(animate);
}

Решение 4: Debounce и Throttle для обработчиков событий

Ограничьте количество вызовов функции:

// Debounce - вызвать ТОЛЬКО после паузы
function debounce(func, delay) {
  let timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func(...args), delay);
  };
}

// Throttle - вызывать НЕ ЧАЩЕ чем каждые N миллисекунд
function throttle(func, limit) {
  let inThrottle;
  return function(...args) {
    if (!inThrottle) {
      func(...args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// Использование
input.addEventListener('input', debounce((e) => {
  // API запрос будет отправлен только ПОСЛЕ того как пользователь перестанет печатать
  searchAPI(e.target.value);
}, 300));

window.addEventListener('resize', throttle(() => {
  // Обновлять layout не чаще чем каждые 250ms
  updateLayout();
}, 250));

Решение 5: Batch обновления в React

В React используйте batch updates для оптимизации рендеров:

function Component() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  const handleClick = () => {
    // React автоматически батчит эти обновления
    setCount(count + 1);
    setText('updated');
    // Один рендер вместо двух!
  };

  // Для асинхронных операций используйте flushSync
  const handleAsync = async () => {
    const data = await fetchData();
    // flushSync гарантирует синхронное обновление
    flushSync(() => setCount(data.count));
  };

  return <button onClick={handleClick}>Click</button>;
}

Чеклист оптимизации

  1. Избегайте больших циклов в главном потоке
  2. Используйте Web Workers для тяжелых вычислений
  3. Разбивайте работу с setTimeout/requestAnimationFrame
  4. Применяйте debounce/throttle на обработчики событий
  5. Используйте batch updates в React
  6. Профилируйте с помощью DevTools Performance tab
  7. Не забывайте про асинхронные операции (fetch, файлы)

Главная идея: если операция занимает больше 16мс, она начинает вредить плавности (при 60 FPS каждый кадр занимает ~16.67ms). Разбивайте такие операции!

Как избежать блокировки Event Loop? | PrepBro