Что такое Web Worker — и почему await не спасает страницу от зависания

Смотри, знакомая картина. Ты нажимаешь «Обработать» на странице с большим файлом. Спиннер замирает. Кнопка «Отмена» не нажимается. Даже текст не выделяется. Через пять секунд всё оживает разом.
Самое обидное: функция была async, и ты честно поставил await. Не помогло. Сейчас разберёмся почему — и что помогает на самом деле.
Почему страница замирает
Весь твой JavaScript на странице выполняется в одном потоке — главном. В этом же потоке браузер рисует страницу и обрабатывает клики.
Представь кафе с одним официантом. Он принимает заказы, разносит блюда и считает выручку. Пока он пять минут пересчитывает кассу, к нему не подойти. Зал «завис».
А что делает await? Он хорош, когда ты ждёшь — ответа сервера, таймера, файла. Пока идёт ожидание, поток свободен. Подробно это в статье про асинхронность.
Но цикл на миллион строк — не ожидание, а работа. Её всё равно кто-то должен сделать. И делает её тот же единственный официант. async перед функцией не нанимает второго.
Web Worker — это второй поток
Web Worker — это отдельный файл с JavaScript, который браузер запускает в своём потоке, рядом с главным. Это второй повар на кухне. Он месит тесто сколько угодно, а официант тем временем обслуживает зал.
Создаётся он одной строкой. Вот как это выглядит в проекте на Vite:
// main.js — страница
const worker = new Worker(new URL("./worker.js", import.meta.url), { type: "module" });
worker.postMessage(bigList); // отдали работу
worker.onmessage = (e) => {
console.log("готово:", e.data); // получили результат
};
// worker.js — второй поток
self.onmessage = (e) => {
const result = heavySort(e.data); // хоть 5 секунд — страница живая
self.postMessage(result);
};
Всё общение — через сообщения: postMessage туда, onmessage обратно. Как записки через окно раздачи.
Три вещи, которые удивляют всех
1. У воркера нет страницы. В нём нет document и window. Он не может сам поменять кнопку или текст — DOM живёт только в главном потоке. Воркер считает и отдаёт результат, а рисует уже страница.
2. Данные копируются, а не передаются. Когда ты шлёшь массив в воркер, браузер делает его полную копию. Поменяешь копию в воркере — на странице ничего не изменится. А огромный массив копировать долго, иногда дольше самой работы.
3. Копию можно заменить передачей. Для двоичных данных (ArrayBuffer) есть трюк: отдать их «из рук в руки».
worker.postMessage(buffer, [buffer]);
console.log(buffer.byteLength); // 0 — у страницы буфера больше нет
Копирования нет, передача мгновенная. Но у отправителя остаётся пустой буфер на 0 байт. Если дальше в коде страница читает его — получишь нули и долго будешь искать баг.
4. Воркер сам не уходит. Поток живёт, пока его не остановят. Частая ошибка — создавать new Worker(...) на каждый клик. Через сотню кликов у тебя сотня потоков, и вкладка ест память. Создай воркер один раз и переиспользуй его. А когда он больше не нужен — worker.terminate().
Когда воркер нужен, а когда нет
Воркер — не бесплатный. Запуск отдельного потока и пересылка данных тоже стоят времени. Правило на глаз:
- Нужен, если операция заметно тормозит интерфейс: парсинг большого CSV, обработка фото, сжатие, поиск по десяткам тысяч записей.
- Нужен для ИИ прямо в браузере. Библиотеки вроде Transformers.js советуют запускать модель в воркере — иначе страница встанет на время ответа.
- Не нужен для запросов к серверу. Это ожидание, с ним справляется
await. - Не нужен, если задача идёт 20 миллисекунд. Часто её дешевле ускорить: например, убрать вложенный цикл. Как это найти — в статье про сложность алгоритмов.
Как проверить, что проблема в главном потоке: открой DevTools → Performance, запиши клик. Длинная серая полоса с красным уголком — это Long Task. Всё, что дольше 50 мс, пользователь чувствует как подтормаживание.
Web Worker и Service Worker — это одно и то же?
Нет. Web Worker нужен твоей странице для тяжёлых вычислений и живёт, пока открыта вкладка. Service Worker стоит между сайтом и сетью: кэширует файлы и работает офлайн.
Можно ли из воркера сделать fetch-запрос?
Да. В воркере есть fetch, таймеры, IndexedDB и WebSocket. Нет только того, что связано со страницей: DOM, window, localStorage.
Как попросить ИИ вынести код в воркер?
Скажи конкретно: «вынеси функцию X в Web Worker, страница шлёт данные через postMessage, ArrayBuffer передавай через transfer». Без этого ИИ часто просто добавит async — а это, как мы выяснили, не поможет.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.





