← Назад к вопросам
Как заблокировать поток в JS?
2.0 Middle🔥 191 комментариев
#JavaScript Core#Браузер и сетевые технологии
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Блокирование потока в JavaScript: синхронный код и event loop
JavaScript однопоточный язык, который работает на основе Event Loop. "Блокирование потока" означает выполнение длительной синхронной операции, которая останавливает обработку других задач. Это критически важная концепция для понимания производительности.
Как работает Event Loop
┌─────────────────────────────────┐
│ Call Stack (Main Thread) │ <- Выполняет текущий код
├─────────────────────────────────┤
│ Event Loop │ <- Проверяет очереди
├─────────────────────────────────┤
│ Callback Queue (Macrotasks) │ <- setTimeout, setInterval
│ Microtask Queue (Promises) │ <- Promise.then(), async/await
└─────────────────────────────────┘
Пример блокирования потока
// НЕПРАВИЛЬНО: синхронный код блокирует поток
function expensiveCalculation() {
let sum = 0;
for (let i = 0; i < 10_000_000_000; i++) {
sum += i;
}
return sum;
}
console.log('Start');
expensiveCalculation(); // Это займет ~5-10 секунд!
console.log('End'); // Выполнится только после завершения
// Во время вычисления UI ЗАМОРОЖЕН:
// - Не работают клики
// - Не скроллится страница
// - Не воспроизводится видео
Решение 1: Разделение на chunks с setTimeout
// ПРАВИЛЬНО: асинхронное выполнение
function expensiveCalculationAsync() {
let sum = 0;
let i = 0;
const chunkSize = 100_000_000;
return new Promise((resolve) => {
function processChunk() {
const endI = Math.min(i + chunkSize, 10_000_000_000);
for (; i < endI; i++) {
sum += i;
}
if (i < 10_000_000_000) {
// Отдаём контроль браузеру перед следующим chunk
setTimeout(processChunk, 0);
} else {
resolve(sum);
}
}
processChunk();
});
}
console.log('Start');
expensiveCalculationAsync().then(result => {
console.log('Result:', result);
console.log('End');
});
// Вывод сразу:
// Start
// ... UI остаётся отзывчивым ...
// Result: 49999999950000000
// End
Решение 2: Web Workers (рекомендуется для тяжелых вычислений)
Web Workers запускают код в отдельном потоке, не блокируя основной поток.
// main.js
console.log('Start');
const worker = new Worker('worker.js');
worker.postMessage({ n: 10_000_000_000 });
worker.onmessage = (event) => {
console.log('Result:', event.data);
console.log('End'); // Выполнится когда worker закончит
};
// UI остаётся полностью отзывчивым!
// worker.js
self.onmessage = (event) => {
const { n } = event.data;
let sum = 0;
// Это выполняется в отдельном потоке
for (let i = 0; i < n; i++) {
sum += i;
}
// Отправляем результат обратно
self.postMessage(sum);
};
Использование в React:
// hooks/useExpensiveCalculation.ts
export function useExpensiveCalculation(data: number[]) {
const [result, setResult] = useState<number | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
const worker = new Worker(new URL('../workers/calculation.js', import.meta.url), {
type: 'module'
});
worker.postMessage(data);
worker.onmessage = (event) => {
setResult(event.data);
setLoading(false);
};
return () => worker.terminate();
}, [data]);
return { result, loading };
}
// Использование
export function DataAnalysis() {
const data = Array.from({ length: 1_000_000 }, (_, i) => i);
const { result, loading } = useExpensiveCalculation(data);
if (loading) return <div>Calculating...</div>;
return <div>Result: {result}</div>;
}
Решение 3: requestAnimationFrame для визуальных обновлений
// Обновление DOM частями
function updateLargeList(items) {
let index = 0;
const container = document.getElementById('list');
function renderChunk() {
const end = Math.min(index + 100, items.length);
for (; index < end; index++) {
const li = document.createElement('li');
li.textContent = items[index];
container.appendChild(li);
}
if (index < items.length) {
// Отдаём контроль браузеру для reflow/repaint
requestAnimationFrame(renderChunk);
}
}
requestAnimationFrame(renderChunk);
}
updateLargeList(new Array(10_000).fill(0).map((_, i) => `Item ${i}`));
Решение 4: Debouncing / Throttling для обработчиков событий
// Блокирующий обработчик
window.addEventListener('scroll', () => {
// Это выполнится СОТНИ раз в секунду!
expensiveCalculation();
});
// Правильно: throttle
function throttle(func, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
window.addEventListener('scroll', throttle(() => {
expensiveCalculation();
}, 1000)); // Максимум раз в секунду
Как определить блокирование потока
// Инструменты отладки
console.time('calculation');
expensiveCalculation();
console.timeEnd('calculation'); // calculation: 5234.12ms
// Performance API
const start = performance.now();
expensiveCalculation();
const end = performance.now();
console.log(`Took ${end - start}ms`);
// Chrome DevTools: Performance tab
// - Красные блоки = Main Thread заблокирован
// - Ищи длительные Task в flame chart
Пример React компонента с блокированием
// Неправильно
export function BadComponent() {
const [count, setCount] = useState(0);
const handleClick = () => {
// Это блокирует поток на 5 секунд!
const sum = Array.from({ length: 10_000_000_000 }, (_, i) => i)
.reduce((a, b) => a + b, 0);
setCount(sum);
};
return <button onClick={handleClick}>Calculate ({count})</button>;
}
// Правильно
export function GoodComponent() {
const [count, setCount] = useState(0);
const [loading, setLoading] = useState(false);
const handleClick = async () => {
setLoading(true);
// Отдаём контроль браузеру перед вычислением
await new Promise(resolve => setTimeout(resolve, 0));
const sum = await new Promise<number>(resolve => {
const worker = new Worker('calc.js');
worker.postMessage(10_000_000_000);
worker.onmessage = (e) => resolve(e.data);
});
setCount(sum);
setLoading(false);
};
return (
<button onClick={handleClick} disabled={loading}>
{loading ? 'Calculating...' : `Calculate (${count})`}
</button>
);
}
Таблица способов избежать блокирования
| Способ | Когда использовать | Плюсы | Минусы |
|---|---|---|---|
| setTimeout | Небольшие операции | Просто | Медленно |
| Web Worker | Тяжелые вычисления | Настоящий многопоток | Сложнее, overhead |
| requestAnimationFrame | DOM обновления | Синхронизация с браузером | Только для анимаций |
| Debounce/Throttle | Обработчики событий | Экономит CPU | Задержка в обработке |
| async/await | Асинхронные операции | Читаемо | Не решает проблему |
Итог
Блокирование потока в JavaScript происходит когда:
- Выполняется длительный синхронный код
- Воспроизводится тяжелый JavaScript
- Выполняются сложные DOM операции
Как избежать:
- Разделяй на chunks с setTimeout
- Используй Web Workers для тяжелых вычислений
- requestAnimationFrame для DOM обновлений
- Профилируй с Chrome DevTools Performance tab
- Избегай синхронных циклов в обработчиках событий
Это критично для создания отзывчивого пользовательского интерфейса.