Service Worker — это особый вид Web Worker, который работает как прокси-сервер между браузером и сетью. Он перехватывает все сетевые запросы страницы и может отвечать из кэша, изменять запросы, отправлять push-уведомления — даже когда страница закрыта.
Service Worker проходит три фазы:
1. Регистрация
// Главный поток
if ('serviceWorker' in navigator) {
const reg = await navigator.serviceWorker.register('/sw.js')
console.log('SW зарегистрирован:', reg.scope)
}2. Установка (install)
Срабатывает один раз при первой регистрации или при обновлении файла sw.js. Здесь заполняется кэш:
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then(cache => cache.addAll(['/index.html', '/app.js']))
)
})3. Активация (activate)
Срабатывает когда SW берёт управление. Здесь удаляются старые кэши:
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== 'v1').map(k => caches.delete(k)))
)
)
})self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then(cached => {
return cached || fetch(event.request)
})
)
})Cache First — сначала кэш, потом сеть. Максимальная скорость, подходит для статики (CSS, JS, шрифты).
Network First — сначала сеть, при ошибке — кэш. Подходит для API с актуальными данными.
Stale While Revalidate — отдаём из кэша немедленно, параллельно обновляем кэш из сети. Баланс скорости и свежести данных.
Cache Only — только кэш, никакой сети. Полностью офлайн-контент.
Network Only — только сеть, без кэширования. Для аналитики, платёжных запросов.
Background Sync позволяет откладывать действия до восстановления соединения. Пользователь отправил форму офлайн — данные сохранятся и уйдут на сервер, когда появится интернет:
// Главный поток — регистрируем синхронизацию
const reg = await navigator.serviceWorker.ready
await reg.sync.register('send-message')
// SW — выполняем при восстановлении сети
self.addEventListener('sync', (event) => {
if (event.tag === 'send-message') {
event.waitUntil(sendQueuedMessages())
}
})Service Worker может получать push-уведомления с сервера даже когда страница закрыта. Сервер отправляет push через Web Push Protocol на endpoint, полученный от браузера при подписке пользователя.
В Chrome DevTools → Application → Service Workers можно: видеть статус SW, принудительно обновить, симулировать офлайн. Вкладка Cache Storage показывает всё закэшированное.
Симуляция Service Worker с перехватом fetch и стратегией cache-first
// Симулируем Cache API через Map
class SimulatedCache {
constructor() { this._store = new Map() }
async put(url, response) {
this._store.set(url, { ...response, cachedAt: Date.now() })
console.log(`[Cache] Сохранено: ${url}`)
}
async match(url) {
return this._store.get(url) || null
}
async addAll(urls) {
for (const url of urls) {
// Симулируем предзагрузку ресурсов
await this.put(url, { status: 200, body: `content of ${url}` })
}
}
get size() { return this._store.size }
keys() { return [...this._store.keys()] }
}
// Симулируем сеть
async function simulatedNetwork(url) {
if (url.includes('offline')) throw new Error('Network error')
return { status: 200, body: `fresh content from network: ${url}` }
}
// Сам Service Worker симулятор
class ServiceWorkerSimulator {
constructor() {
this._cache = new SimulatedCache()
this._strategy = 'cache-first'
this._installed = false
this._active = false
}
async install(assets) {
console.log('[SW] Установка...')
await this._cache.addAll(assets)
this._installed = true
console.log(`[SW] Установлен. Закэшировано ресурсов: ${this._cache.size}`)
}
async activate() {
if (!this._installed) throw new Error('SW не установлен')
this._active = true
console.log('[SW] Активирован, контролирует страницу')
}
// Cache-First стратегия
async fetch(url) {
if (!this._active) {
return simulatedNetwork(url)
}
if (this._strategy === 'cache-first') {
const cached = await this._cache.match(url)
if (cached) {
console.log(`[SW] Из кэша: ${url}`)
return cached
}
console.log(`[SW] Кэш пуст, запрос в сеть: ${url}`)
try {
const response = await simulatedNetwork(url)
await this._cache.put(url, response) // сохраняем для следующего раза
return response
} catch (err) {
return { status: 503, body: 'Офлайн и нет в кэше' }
}
}
if (this._strategy === 'network-first') {
try {
const response = await simulatedNetwork(url)
await this._cache.put(url, response)
console.log(`[SW] Из сети (обновлён кэш): ${url}`)
return response
} catch {
const cached = await this._cache.match(url)
if (cached) {
console.log(`[SW] Сеть недоступна, из кэша: ${url}`)
return cached
}
return { status: 503, body: 'Офлайн' }
}
}
}
}
// Демо
const sw = new ServiceWorkerSimulator()
await sw.install(['/index.html', '/app.js', '/styles.css'])
await sw.activate()
console.log('\n--- Cache-First запросы ---')
const r1 = await sw.fetch('/index.html') // из кэша
console.log('Ответ:', r1.body)
const r2 = await sw.fetch('/api/data') // не в кэше → сеть → кэш
console.log('Ответ:', r2.body)
const r3 = await sw.fetch('/api/data') // теперь из кэша
console.log('Ответ:', r3.body)
console.log('\n--- Офлайн сценарий ---')
sw._strategy = 'network-first'
const r4 = await sw.fetch('/offline-resource') // сеть упала, кэш пуст
console.log('Ответ:', r4.body)Service Worker — это особый вид Web Worker, который работает как прокси-сервер между браузером и сетью. Он перехватывает все сетевые запросы страницы и может отвечать из кэша, изменять запросы, отправлять push-уведомления — даже когда страница закрыта.
Service Worker проходит три фазы:
1. Регистрация
// Главный поток
if ('serviceWorker' in navigator) {
const reg = await navigator.serviceWorker.register('/sw.js')
console.log('SW зарегистрирован:', reg.scope)
}2. Установка (install)
Срабатывает один раз при первой регистрации или при обновлении файла sw.js. Здесь заполняется кэш:
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then(cache => cache.addAll(['/index.html', '/app.js']))
)
})3. Активация (activate)
Срабатывает когда SW берёт управление. Здесь удаляются старые кэши:
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== 'v1').map(k => caches.delete(k)))
)
)
})self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then(cached => {
return cached || fetch(event.request)
})
)
})Cache First — сначала кэш, потом сеть. Максимальная скорость, подходит для статики (CSS, JS, шрифты).
Network First — сначала сеть, при ошибке — кэш. Подходит для API с актуальными данными.
Stale While Revalidate — отдаём из кэша немедленно, параллельно обновляем кэш из сети. Баланс скорости и свежести данных.
Cache Only — только кэш, никакой сети. Полностью офлайн-контент.
Network Only — только сеть, без кэширования. Для аналитики, платёжных запросов.
Background Sync позволяет откладывать действия до восстановления соединения. Пользователь отправил форму офлайн — данные сохранятся и уйдут на сервер, когда появится интернет:
// Главный поток — регистрируем синхронизацию
const reg = await navigator.serviceWorker.ready
await reg.sync.register('send-message')
// SW — выполняем при восстановлении сети
self.addEventListener('sync', (event) => {
if (event.tag === 'send-message') {
event.waitUntil(sendQueuedMessages())
}
})Service Worker может получать push-уведомления с сервера даже когда страница закрыта. Сервер отправляет push через Web Push Protocol на endpoint, полученный от браузера при подписке пользователя.
В Chrome DevTools → Application → Service Workers можно: видеть статус SW, принудительно обновить, симулировать офлайн. Вкладка Cache Storage показывает всё закэшированное.
Симуляция Service Worker с перехватом fetch и стратегией cache-first
// Симулируем Cache API через Map
class SimulatedCache {
constructor() { this._store = new Map() }
async put(url, response) {
this._store.set(url, { ...response, cachedAt: Date.now() })
console.log(`[Cache] Сохранено: ${url}`)
}
async match(url) {
return this._store.get(url) || null
}
async addAll(urls) {
for (const url of urls) {
// Симулируем предзагрузку ресурсов
await this.put(url, { status: 200, body: `content of ${url}` })
}
}
get size() { return this._store.size }
keys() { return [...this._store.keys()] }
}
// Симулируем сеть
async function simulatedNetwork(url) {
if (url.includes('offline')) throw new Error('Network error')
return { status: 200, body: `fresh content from network: ${url}` }
}
// Сам Service Worker симулятор
class ServiceWorkerSimulator {
constructor() {
this._cache = new SimulatedCache()
this._strategy = 'cache-first'
this._installed = false
this._active = false
}
async install(assets) {
console.log('[SW] Установка...')
await this._cache.addAll(assets)
this._installed = true
console.log(`[SW] Установлен. Закэшировано ресурсов: ${this._cache.size}`)
}
async activate() {
if (!this._installed) throw new Error('SW не установлен')
this._active = true
console.log('[SW] Активирован, контролирует страницу')
}
// Cache-First стратегия
async fetch(url) {
if (!this._active) {
return simulatedNetwork(url)
}
if (this._strategy === 'cache-first') {
const cached = await this._cache.match(url)
if (cached) {
console.log(`[SW] Из кэша: ${url}`)
return cached
}
console.log(`[SW] Кэш пуст, запрос в сеть: ${url}`)
try {
const response = await simulatedNetwork(url)
await this._cache.put(url, response) // сохраняем для следующего раза
return response
} catch (err) {
return { status: 503, body: 'Офлайн и нет в кэше' }
}
}
if (this._strategy === 'network-first') {
try {
const response = await simulatedNetwork(url)
await this._cache.put(url, response)
console.log(`[SW] Из сети (обновлён кэш): ${url}`)
return response
} catch {
const cached = await this._cache.match(url)
if (cached) {
console.log(`[SW] Сеть недоступна, из кэша: ${url}`)
return cached
}
return { status: 503, body: 'Офлайн' }
}
}
}
}
// Демо
const sw = new ServiceWorkerSimulator()
await sw.install(['/index.html', '/app.js', '/styles.css'])
await sw.activate()
console.log('\n--- Cache-First запросы ---')
const r1 = await sw.fetch('/index.html') // из кэша
console.log('Ответ:', r1.body)
const r2 = await sw.fetch('/api/data') // не в кэше → сеть → кэш
console.log('Ответ:', r2.body)
const r3 = await sw.fetch('/api/data') // теперь из кэша
console.log('Ответ:', r3.body)
console.log('\n--- Офлайн сценарий ---')
sw._strategy = 'network-first'
const r4 = await sw.fetch('/offline-resource') // сеть упала, кэш пуст
console.log('Ответ:', r4.body)Реализуй createServiceWorkerSimulator() — объект с тремя методами: install(assets) принимает массив URL и «кэширует» их (сохраняет в Map); fetch(url) реализует стратегию cache-first: сначала ищет в кэше, при отсутствии возвращает `{ status: 200, body: "network: " + url }` и сохраняет в кэш; getCache() возвращает массив закэшированных URL.
Используй Map для хранения кэша. install() сохраняет каждый URL как ключ со значением { status: 200, body: "cached: " + url }. fetch() проверяет cache.has(url) и возвращает cache.get(url) при попадании. getCache() возвращает [...cache.keys()].