Понятно объясняем, что такое Web Worker и Service Worker: чем они отличаются, как работают, когда нужны, и примеры типичных сценариев в веб‑приложениях.

Когда веб‑приложение начинает «тормозить», у проблемы часто две причины.
CPU: тяжёлые вычисления (обработка больших массивов, шифрование, генерация отчётов) блокируют главный поток, и интерфейс перестаёт отвечать.
Сеть: запросы, кэш и офлайн‑поведение работают непредсказуемо — из‑за этого страница грузится медленно или вовсе не открывается без интернета.
В браузере для этих случаев есть два разных инструмента с похожими названиями — и их легко перепутать.
Web Worker — способ вынести вычисления из основного потока JavaScript. Он помогает сохранить плавность UI, когда нужно много считать, парсить, преобразовывать или фильтровать данные.
Service Worker — «прокси» между страницей и сетью. Он перехватывает запросы (через Fetch API), управляет кэшем, включает офлайн‑режим, ускоряет повторные загрузки и поддерживает возможности PWA вроде push‑уведомлений.
Важно: оба воркера работают не «вместо» вашего приложения, а рядом с ним — и каждый решает свою задачу.
Достаточно базовых навыков:
Разберём, как устроено выполнение JavaScript в браузере, что умеют Web Worker и Service Worker, какие у них ограничения, и в каких сценариях они действительно дают выигрыш по performance фронтенда. В конце — короткие примеры кода и чек‑лист выбора.
Когда вы открываете страницу, браузер создаёт для неё главный поток выполнения. В нём по очереди выполняются задачи: обработка кликов и скролла, запуск JavaScript‑кода, пересчёт стилей, раскладка элементов и отрисовка. Такой подход упрощает модель программирования: меньше гонок данных и неожиданных конфликтов, потому что в один момент времени выполняется одна «порция» JS.
Важно: «однопоточность» здесь — практическое правило для кода страницы. Внутри самого браузера много параллельных процессов и потоков (сеть, декодирование, композитинг), но ваш JS на странице обычно живёт в одном основном контексте.
Главный поток — это место, где решается всё, что пользователь видит и ощущает:
Браузер старается выдавать кадры регулярно (условно около 60 раз в секунду). Чтобы анимации и прокрутка были плавными, на подготовку одного кадра остаются миллисекунды. Если JS занимает главный поток слишком долго, браузер просто не успевает вовремя перерисовать интерфейс.
Под «тяжёлыми» задачами обычно понимают:
Если такой код выполняется, скажем, 200–500 мс без пауз, пользователь увидит задержку: клики «не срабатывают», прокрутка рвётся, анимации дёргаются. Это и есть jank — визуальная «неровность», вызванная блокировкой главного потока.
Параллельность в вебе обычно достигается не запуском второго «потока JavaScript» внутри той же страницы, а переносом части работы в отдельный контекст. Например, Web Worker выполняет код отдельно от главного потока и общается с ним сообщениями.
Это даёт практический эффект: тяжёлые вычисления не мешают UI. Но есть нюанс: воркер не делит со страницей память и DOM напрямую (чаще всего данные копируются или передаются как transferable). Поэтому это не «два потока, которые одновременно меняют одни и те же переменные», а два изолированных окружения с явным обменом данными.
Web Worker — способ выполнять JavaScript «в фоне», в отдельном потоке, не блокируя основной поток страницы. Если в основном потоке вы рисуете интерфейс, обрабатываете клики и анимации, то воркеру можно поручить тяжёлые вычисления: парсинг больших данных, фильтрацию, работу с криптографией, подготовку отчётов.
Важно: Web Worker не ускоряет JavaScript «магически» — он просто позволяет вынести часть работы из главного потока, чтобы интерфейс оставался отзывчивым.
Dedicated Worker (выделенный) — самый популярный вариант. Привязан к конкретной вкладке/странице (контексту), которая его создала. Если страницу закрыть — воркер завершится.
Shared Worker (общий) — может быть разделяемым между несколькими вкладками/iframe одного происхождения (origin). Полезен, когда нужно единое «фоновое состояние» на несколько открытых страниц (например, общий канал обмена или единый расчётный процесс).
Worklet (упоминание) — близкая, но отдельная история: специальные мини‑воркеры для конкретных задач (например, AudioWorklet для аудио или PaintWorklet для кастомной отрисовки). Их чаще используют точечно и не как универсальную «фоновую машину».
Воркер создают из основного кода так:
const worker = new Worker('/workers/calc-worker.js', { type: 'module' });
Ключевой момент: код воркера живёт в отдельном файле (или модуле). Это помогает браузеру изолировать выполнение и не давать воркеру прямого доступа к DOM.
Связь идёт сообщениями:
worker.postMessage(data)postMessage(result)messageМодель похожа на «почтовый ящик»: вы отправляете данные и получаете ответ асинхронно, не блокируя UI.
По умолчанию используется механизм structured clone — браузер копирует поддерживаемые структуры данных (объекты, массивы, строки, Map/Set, даты и т. п.).
Если данные большие, лучше использовать Transferable (например, ArrayBuffer): тогда владение буфером передаётся воркеру без копирования, что заметно ускоряет обмен.
Ограничение для ожиданий: функции и DOM‑элементы так передавать нельзя — воркер работает в отдельной среде и не имеет доступа к дереву страницы.
Web Worker часто воспринимают как «второй поток для JavaScript», но у него есть важные ограничения. Это не недостатки, а осознанные правила безопасности и предсказуемости: воркер должен считать данные, не ломая интерфейс.
Воркеры не видят window, document, элементы страницы и не могут напрямую менять верстку. Любые результаты нужно возвращать в основной поток сообщениями (postMessage), а уже там обновлять интерфейс.
Доступный набор API отличается от основного потока. Обычно доступны таймеры, fetch, работа с бинарными данными, Web Crypto, иногда — WebSocket.
Зато часто недоступны или ограничены:
localStorage и другие синхронные хранилища (ориентируйтесь на IndexedDB);Практическое правило: если API «про интерфейс» или «про синхронные блокировки», скорее всего, в воркере его не будет.
Скрипт воркера загружается как отдельный ресурс и подчиняется правилам происхождения (origin). Если вы пытаетесь подключить воркер с другого домена, понадобятся корректные CORS‑заголовки. В сборках с модулями удобнее создавать воркер через URL относительно текущего файла — так меньше сюрпризов с путями.
Логи из воркера попадают в DevTools, но в отдельный контекст. Ищите разделы вроде Sources → Workers и запускайте профилирование в Performance, чтобы увидеть, где именно тратится время: в основном потоке или в воркере.
Две самые частые проблемы:
requestId, явные состояния и протокол сообщений.postMessage копирует объекты (structured clone), и большие массивы могут стать узким местом. Для бинарных данных используйте Transferable (например, ArrayBuffer), чтобы передавать владение без копии.Web Worker стоит подключать там, где код реально нагружает CPU и из‑за этого «заикается» интерфейс: прокрутка, ввод, анимации, клики. Воркеры помогают вынести вычисления в отдельный поток и вернуть отзывчивость странице — особенно заметно на слабых устройствах.
Типичные кандидаты:
Если вы видите в профилировщике длинные Long task на основном потоке — это почти прямой сигнал попробовать Web Worker.
Частый сценарий — импорт данных: пользователь загружает CSV/JSON, а страница на несколько секунд перестаёт реагировать. Парсинг, валидацию и предварительную обработку удобно делать в воркере, а в основной поток отправлять только результат (например, уже нормализованные записи или статистику).
Декодирование, ресайз, фильтры и другие операции над изображениями/медиа тоже нередко упираются в CPU. В браузере это зависит от API: часть операций можно ускорить через воркеры, но иногда удобнее полагаться на встроенные механизмы (например, аппаратное декодирование) и не усложнять архитектуру.
Если вы передаёте большие бинарные массивы (аудио, данные для графиков, результаты вычислений), используйте Transferable‑объекты, например ArrayBuffer. В отличие от копирования, «передача владения» почти не тратит время и память, что напрямую влияет на performance фронтенда.
Web Worker не ускорит задачу, которая:
Практичное правило: выносите в Web Worker всё, что «жжёт CPU» и мешает интерфейсу, а в основном потоке оставляйте то, что связано с отображением и быстрыми реакциями.
Service Worker — специальный скрипт, который браузер запускает в фоне и использует как «прокси» между вашими страницами и сетью. Он не имеет доступа к DOM (не может напрямую менять HTML), зато умеет перехватывать сетевые запросы и выполнять фоновые задачи по событиям.
Когда страница делает запрос (например, через fetch или загрузку ресурсов), Service Worker может получить событие и решить, что вернуть: ответ из сети, из кэша или сгенерировать ответ самостоятельно. Это не «ускоритель по умолчанию», а управляемый механизм: логику выбираете вы.
Жизненный цикл сделан так, чтобы обновления происходили контролируемо: новый воркер устанавливается отдельно от активного и включается по правилам браузера.
Service Worker реагирует на события. Самые известные:
Поддержка отдельных возможностей может отличаться между браузерами и платформами.
Service Worker действует только в пределах scope — набора URL, обычно это путь, где лежит файл воркера, и всё «ниже» по дереву. Поэтому размещение файла и параметры регистрации напрямую определяют, какие страницы и запросы он сможет контролировать.
Service Worker не привязан к конкретной вкладке: его может запустить браузер «по событию» даже когда сайт не открыт на экране. Он живёт отдельной фоновой сущностью и просыпается, когда нужно обработать событие, после чего может быть выгружен для экономии ресурсов.
Service Worker полезен там, где хочется контролировать сеть: ускорять загрузку, переживать нестабильный интернет и точнее управлять обновлениями. По сути это «сетевой прокси» внутри браузера: он может перехватывать запросы (fetch) и решать, откуда отдать ресурс — из сети или из кэша.
В отличие от обычного HTTP‑кэша, Cache Storage управляется вашим кодом. Это позволяет выбрать стратегию под конкретный тип данных:
Важно: кэшировать можно не только ассеты, но и ответы API — если вы осознанно задаёте срок жизни и правила инвалидации.
Service Worker позволяет:
Но офлайн — это не магия: если данные не были заранее закэшированы или сохранены (например, в IndexedDB), показать их будет нечем.
Можно заранее прогреть кэш при установке воркера (precache) и обновлять его при активации новой версии. Это ускоряет повторные визиты и уменьшает «дрожание» интерфейса при загрузке.
Концепция background sync — отложить отправку действий пользователя до момента, когда сеть снова станет стабильной. На практике поддержка и поведение зависят от браузера и настроек энергосбережения, поэтому это скорее улучшение, а не гарантированный канал доставки.
Главные риски — устаревший кэш и неочевидные обновления. Пользователь может долго оставаться на старых файлах, если не продуманы версионирование и очистка. Хорошее правило: обновлять кэш предсказуемо, явно удалять старые записи и иметь понятную стратегию отката, если новая версия сломалась.
Service Worker получает особые права: он может перехватывать сетевые запросы страницы и отвечать из кэша. Поэтому у него строгая модель безопасности и отдельный жизненный цикл обновлений — важно понимать оба аспекта, чтобы не получить «сломанный офлайн» или неожиданные баги.
Service Worker работает только на защищённом контексте: HTTPS обязателен, исключение — localhost для разработки.
Причина проста: если бы воркер мог регистрироваться по HTTP, злоумышленник в сети (Wi‑Fi в кафе, корпоративный прокси) смог бы подменить скрипт воркера и надолго закрепиться в браузере пользователя.
Перехват через fetch даёт большую силу — и большой риск:
/profile) и показать их не тому пользователю;Хорошая практика: кэшировать только то, что действительно публично и версионируется (статику), а для API — аккуратные правила и явные исключения.
Когда вы публикуете новую версию, браузер скачивает новый Service Worker, но часто оставляет его в состоянии waiting, пока все вкладки со старой версией не будут закрыты.
skipWaiting() уместен, если исправление критичное и вы готовы к возможному «перезапуску» логики в активной вкладке.clients.claim() полезен, чтобы новый воркер сразу начал контролировать открытые страницы после активации.Не включайте их «по умолчанию» без понимания UX: внезапная смена кэша посреди сессии может привести к несовместимости ресурсов.
Используйте версионированные имена кэшей (например, app-cache-v3) и удаляйте старые в обработчике activate. Тогда обновления становятся предсказуемыми.
Проверяйте состояние воркера и содержимое хранилищ в DevTools → Application → Service Workers / Cache Storage: там видно, кто активен, кто ждёт, какие кэши созданы и что именно лежит внутри.
Оба «воркера» запускаются отдельно от основного потока страницы, но решают разные задачи.
Web Worker нужен, чтобы вынести тяжёлые вычисления (парсинг, обработку данных, кодирование, поиск, криптографию) из UI‑потока и не «фризить» интерфейс.
Service Worker — прослойка между страницей и сетью. Он перехватывает запросы, управляет кэшем, включает офлайн‑режим, ускоряет повторные загрузки и обслуживает PWA‑сценарии.
Web Worker обычно живёт пока живёт страница (или пока вы его не остановите). Он привязан к конкретной вкладке.
Service Worker устанавливается и активируется для origin (сайта) и может переживать перезагрузки страниц. Он запускается «по событию» (fetch, push, sync) и может быстро завершаться, когда не нужен — поэтому в нём важно думать событиями, а не бесконечными циклами.
И Web Worker, и Service Worker не имеют прямого доступа к DOM.
При этом Web Worker ориентирован на вычисления и работу с данными (например, ArrayBuffer/TypedArray).
Service Worker работает с сетевыми и офлайн‑API: перехват запросов, Cache Storage, интеграция с push‑уведомлениями и фоновыми задачами (в зависимости от поддержки браузера). При этом он плохо подходит для длительных вычислений «в фоне»: для CPU‑задач логичнее Web Worker.
Web Worker общается со страницей через postMessage и события message. Для обмена между несколькими вкладками/контекстами можно использовать BroadcastChannel.
Service Worker тоже может отправлять сообщения клиентам (страницам) и принимать их. BroadcastChannel помогает синхронизировать состояние между вкладками, когда нужно уведомить UI об обновлённом кэше или о смене версии.
Web Worker выигрывает, когда нагрузка — CPU, и цена «передачи данных» оправдана. Накладные расходы возникают из‑за сериализации сообщений; их снижают transferables (передача владения буфером).
Service Worker ускоряет повторные визиты и офлайн, но добавляет сложность: установка/активация, стратегия кэширования и риск «устаревшего» контента при неправильном обновлении.
Ниже — два минимальных «скелета», которые помогают быстро проверить идею: Web Worker для тяжёлых вычислений и Service Worker для кэша/офлайна. Примеры намеренно простые, чтобы их легко было адаптировать под любой стек.
Структура файлов:
worker.js:
self.onmessage = (e) => {
const { n } = e.data;
// Условно «тяжёлая» функция
let sum = 0;
for (let i = 0; i < n; i++) sum += Math.sqrt(i);
self.postMessage({ sum });
};
main.js:
const worker = new Worker(new URL('./worker.js', import.meta.url), { type: 'module' });
worker.onmessage = (e) => {
console.log('Готово:', e.data.sum);
};
worker.postMessage({ n: 5_000_000 });
// Когда воркер больше не нужен
// worker.terminate();
Обмен идёт через postMessage и события message. Если передаёте большие массивы, изучите transferable objects (можно передать буфер без копирования).
Важно про расположение: файл sw.js обычно кладут ближе к корню сайта (например, /sw.js), чтобы его область действия покрывала нужные страницы.
main.js (регистрация):
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
sw.js (кэшируем статические запросы):
const CACHE = 'static-v1';
self.addEventListener('install', (event) => {
event.waitUntil(caches.open(CACHE));
});
self.addEventListener('fetch', (event) => {
const req = event.request;
event.respondWith(
caches.match(req).then((cached) => {
return (
cached ||
fetch(req).then((resp) => {
const copy = resp.clone();
caches.open(CACHE).then((c) => c.put(req, copy));
return resp;
})
);
})
);
});
Web Worker часто удобнее подключать через new URL('./worker.js', import.meta.url) — так сборщик сам обработает путь.
Service Worker почти всегда должен быть доступен по фиксированному URL (/sw.js), поэтому проверьте, что сборка копирует его в корень.
Проверьте: офлайн‑режим (в DevTools), поведение при обновлении файлов, запросы к большим данным, и не кэшируются ли случайно API‑ответы, которые должны быть свежими.
Чтобы аккуратно отключить/сбросить Service Worker: DevTools → Application → Service Workers → Unregister и Clear storage. Либо в консоли:
navigator.serviceWorker.getRegistrations()
.then((regs) => regs.forEach((r) => r.unregister()));
Web Worker сбрасывается проще: вызывайте worker.terminate() и пересоздавайте экземпляр.
Иногда выбор простой: вы либо разгружаете интерфейс, либо ускоряете сеть и делаете офлайн. Но в реальных приложениях часто нужны оба подхода — важно понять, где именно узкое место.
Если главный вопрос звучит как «почему страница зависает, пока мы считаем/парсим/сжимаем данные?» — выносите работу из UI‑потока.
Да, и это частая схема:
Вместе это даёт заметный эффект: быстрее загрузка и меньше «фризов» при работе.
Если вы собираете приложение с PWA‑поведением (кэш, офлайн, быстрые повторные загрузки) и при этом у вас есть тяжёлая обработка данных на клиенте (например, импорт CSV, предобработка аналитики, подготовка данных для графиков), часто выгодно заложить оба инструмента сразу: Service Worker как управляемый слой сети и Web Worker как слой CPU‑задач.
В TakProsto.AI такие решения удобно продумывать «сверху вниз»: в режиме планирования (planning mode) заранее разделяете, какие операции должны жить в UI, какие — в Web Worker, а какие запросы и ресурсы имеет смысл отдавать через стратегии кэширования Service Worker. Поскольку TakProsto — vibe‑coding платформа для быстрого создания веб‑приложений (React на фронтенде, Go + PostgreSQL на бэкенде), подход с воркерами помогает сразу держать в фокусе performance и офлайн‑UX, не усложняя основной код интерфейса. А если захотите зафиксировать удачную архитектуру, пригодятся снапшоты и откат (rollback), чтобы безопасно экспериментировать со стратегиями кэша и обновлениями.
Если хотите углубиться, следующие темы обычно дают максимум практической пользы: PWA, стратегии кэширования (Cache First / Network First / Stale‑While‑Revalidate), оптимизация передачи данных между потоками, хранение в IndexedDB, а также push‑уведомления (когда они действительно оправданы).
Web Worker выносит CPU‑тяжёлые вычисления из главного потока, чтобы не «фризить» UI.
Service Worker перехватывает сетевые запросы и управляет кэшем/офлайном (и PWA‑возможностями вроде push).
Если «лагает интерфейс из‑за вычислений» — обычно нужен Web Worker. Если «медленно грузится/не работает без сети» — Service Worker.
Используйте Web Worker, когда видите длинные задачи (Long task) на основном потоке и из‑за этого:
Типичные задачи: сортировки и агрегации больших массивов, парсинг JSON/CSV, криптография, компрессия, расчёты для графиков.
Когда узкое место — не CPU, а ожидание сети:
В таких случаях выигрывает Service Worker (стратегии кэширования, контроль fetch), а Web Worker почти ничего не даст.
Нет: воркеры не имеют прямого доступа к window/document и дереву DOM.
Правильный паттерн такой:
postMessage и обновляет UI.Это делает поведение предсказуемым и не даёт «тяжёлому» коду ломать рендеринг.
По умолчанию postMessage использует structured clone — данные копируются. На больших массивах это может стать узким местом.
Для бинарных данных используйте Transferable (например, ArrayBuffer), чтобы передать владение без копирования:
Это скрипт‑«прокси» внутри браузера, который:
fetch);Cache Storage;Он не ускоряет всё автоматически: ускорение появляется, когда вы задаёте подходящую стратегию кэширования.
Базовая схема такая:
register — страница регистрирует воркер;install — можно подготовить кэши/прекэш;activate — воркер начинает контролировать клиентов (в рамках scope), часто здесь чистят старые кэши.Обновления происходят контролируемо: новый воркер может попасть в состояние , пока не исчезнут вкладки со старой версией.
Популярные стратегии:
Для API кэшируйте только осознанно: продумайте TTL/инвалидацию и исключения для персональных ответов.
Service Worker разрешён только в secure context:
localhost для разработки.Причина практическая: без HTTPS злоумышленник мог бы подменить файл воркера и надолго закрепиться в браузере пользователя (перехватывать запросы, отдавать фальшивые ответы из кэша).
Проверьте и отладьте так:
Если «залипла» старая версия:
Важно: после передачи буфер в отправителе обычно становится «обнулённым» (detached).
Unregister и Clear storage;activate.Для Web Worker ищите отдельный контекст в DevTools (Sources/Workers) и профилируйте, где тратится время — в UI‑потоке или в воркере.