Некоторые веб-сайты могут нуждаться в общении с работником службы без необходимости быть проинформированным о результате. Вот несколько примеров:
- Страница отправляет Service Worker список URL-адресов для предварительной выборки , так что когда пользователь нажимает на ссылку, подресурсы документа или страницы уже доступны в кэше, что значительно ускоряет последующую навигацию.
- Страница просит Service Worker извлечь и кэшировать набор лучших статей, чтобы сделать их доступными для офлайн-целей.
Делегирование таких некритических задач Service Worker имеет то преимущество, что освобождает основной поток для более эффективной обработки более важных задач, таких как реагирование на действия пользователя.

В этом руководстве мы рассмотрим, как реализовать технику односторонней связи от страницы к service worker с помощью стандартных API браузера и библиотеки Workbox. Мы будем называть эти типы вариантов использования императивным кэшированием .
Производственный случай
1-800-Flowers.com реализовал императивное кэширование (предварительную выборку) с помощью Service Worker через postMessage()
для предварительной выборки верхних элементов на страницах категорий с целью ускорения последующей навигации на страницах с подробностями о товарах.

Они используют смешанный подход для принятия решения о том, какие элементы следует предварительно загрузить:
- Во время загрузки страницы они просят сервисный обработчик извлечь данные JSON для первых 9 элементов и добавить полученные объекты ответа в кэш.
- Для остальных элементов они прослушивают событие
mouseover
, чтобы при наведении курсора на элемент можно было «по запросу» запустить выборку ресурса.
Они используют API кэширования для хранения ответов JSON:

Когда пользователь нажимает на элемент, связанные с ним данные JSON могут быть извлечены из кэша без необходимости обращения к сети, что ускоряет навигацию.
Использование Workbox
Workbox предоставляет простой способ отправки сообщений service worker через пакет workbox-window
, набор модулей, предназначенных для запуска в контексте окна. Они являются дополнением к другим пакетам Workbox, которые работают в service worker.
Чтобы связать страницу с Service Worker, сначала получите ссылку на объект Workbox для зарегистрированного Service Worker:
const wb = new Workbox('/sw.js');
wb.register();
Затем вы можете напрямую отправлять сообщение декларативно, без хлопот, связанных с получением регистрации, проверкой активации или размышлениями о базовом API связи:
wb.messageSW({"type": "PREFETCH", "payload": {"urls": ["/data1.json", "data2.json"]}}); });
Service Worker реализует обработчик message
для прослушивания этих сообщений. Он может опционально возвращать ответ, хотя в таких случаях это не обязательно:
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'PREFETCH') {
// do something
}
});
Использование API браузера
Если библиотеки Workbox недостаточно для ваших нужд, вот как можно реализовать взаимодействие окна с работником службы, используя API браузера.
API postMessage можно использовать для установления одностороннего механизма связи от страницы к сервисному работнику.
Страница вызывает postMessage()
в интерфейсе Service Worker:
navigator.serviceWorker.controller.postMessage({
type: 'MSG_ID',
payload: 'some data to perform the task',
});
Сервисный работник реализует обработчик message
для прослушивания этих сообщений.
self.addEventListener('message', (event) => {
if (event.data && event.data.type === MSG_ID) {
// do something
}
});
Атрибут {type : 'MSG_ID'}
не является абсолютно обязательным, но это один из способов разрешить странице отправлять различные типы инструкций сервисному работнику (то есть «предварительно извлечь» и «очистить хранилище»). Сервисный работник может разветвляться на различные пути выполнения на основе этого флага.
Если операция прошла успешно, пользователь сможет получить от нее выгоду, но если нет, это не изменит основной пользовательский поток. Например, когда 1-800-Flowers.com пытается выполнить предварительное кэширование, странице не нужно знать, удалось ли это service worker. Если это так, то пользователь сможет воспользоваться более быстрой навигацией. Если нет, то странице все равно нужно перейти на новую страницу. Это просто займет немного больше времени.
Простой пример предварительной выборки
Одним из наиболее распространенных применений императивного кэширования является предварительная выборка , то есть выборка ресурсов для заданного URL-адреса до того, как пользователь перейдет по нему, с целью ускорения навигации.
Существуют различные способы реализации предварительной загрузки на сайтах:
- Использование тегов предварительной загрузки ссылок на страницах: ресурсы хранятся в кэше браузера в течение пяти минут, после чего применяются обычные правила
Cache-Control
для ресурса. - Дополнение предыдущей методики стратегией кэширования во время выполнения в сервис-воркере для продления срока службы ресурса предварительной выборки сверх этого предела.
Для относительно простых сценариев предварительной загрузки, таких как предварительная загрузка документов или определенных ресурсов (JS, CSS и т. д.), эти методы являются наилучшим подходом.
Если требуется дополнительная логика, например, анализ ресурса предварительной выборки (файла или страницы JSON) для извлечения его внутренних URL-адресов, целесообразнее полностью делегировать эту задачу сервисному работнику.
Делегирование этих типов операций сервисному работнику имеет следующие преимущества:
- Передача тяжелой работы по выборке и обработке после выборки (которая будет представлена позже) во вторичный поток. Благодаря этому основной поток освобождается для обработки более важных задач, таких как реагирование на взаимодействие с пользователем.
- Позволяет нескольким клиентам (например, вкладкам) повторно использовать общую функциональность и даже вызывать службу одновременно, не блокируя основной поток.
Предварительная загрузка страниц с подробностями о продукте
Сначала используйте postMessage()
в интерфейсе Service Worker и передайте массив URL-адресов в кэш:
navigator.serviceWorker.controller.postMessage({
type: 'PREFETCH',
payload: {
urls: [
'www.exmaple.com/apis/data_1.json',
'www.exmaple.com/apis/data_2.json',
],
},
});
В сервис-воркере реализуйте обработчик message
для перехвата и обработки сообщений, отправляемых любой активной вкладкой:
addEventListener('message', (event) => {
let data = event.data;
if (data && data.type === 'PREFETCH') {
let urls = data.payload.urls;
for (let i in urls) {
fetchAsync(urls[i]);
}
}
});
В предыдущем коде мы ввели небольшую вспомогательную функцию fetchAsync()
для итерации по массиву URL-адресов и выдачи запроса на выборку для каждого из них:
async function fetchAsync(url) {
// await response of fetch call
let prefetched = await fetch(url);
// (optionally) cache resources in the service worker storage
}
Когда ответ получен, вы можете положиться на заголовки кэширования ресурса. Однако во многих случаях, например на страницах с подробностями о продукте, ресурсы не кэшируются (что означает, что у них есть заголовок Cache-control
no-cache
). В таких случаях вы можете переопределить это поведение, сохранив извлеченный ресурс в кэше service worker. Это имеет дополнительное преимущество, позволяя обслуживать файл в офлайн-сценариях.
Помимо данных JSON
После того как данные JSON извлечены из конечной точки сервера, они часто содержат другие URL-адреса, которые также стоит предварительно загрузить, например, изображение или другие данные конечной точки, связанные с этими данными первого уровня.
Предположим, что в нашем примере возвращаемые данные JSON представляют собой информацию о сайте по продаже продуктов:
{
"productName": "banana",
"productPic": "https://cdn.example.com/product_images/banana.jpeg",
"unitPrice": "1.99"
}
Измените код fetchAsync()
для итерации по списку продуктов и кэширования главного изображения для каждого из них:
async function fetchAsync(url, postProcess) {
// await response of fetch call
let prefetched = await fetch(url);
//(optionally) cache resource in the service worker cache
// carry out the post fetch process if supplied
if (postProcess) {
await postProcess(prefetched);
}
}
async function postProcess(prefetched) {
let productJson = await prefetched.json();
if (productJson && productJson.product_pic) {
fetchAsync(productJson.product_pic);
}
}
Вы можете добавить обработку исключений вокруг этого кода для ситуаций вроде 404. Но прелесть использования service worker для предварительной выборки в том, что он может дать сбой без особых последствий для страницы и основного потока. Вы также можете иметь более сложную логику в постобработке предварительно выбранного контента, делая его более гибким и отделенным от данных, которые он обрабатывает. Предела нет.
Заключение
В этой статье мы рассмотрели распространенный вариант использования односторонней связи между страницей и service worker: императивное кэширование . Обсуждаемые примеры предназначены только для демонстрации одного способа использования этого шаблона, и тот же подход может быть применен и к другим вариантам использования, например, кэширование лучших статей по запросу для офлайн-потребления, закладок и т. д.
Для получения дополнительных шаблонов общения между пейджерами и работниками служб ознакомьтесь с:
- Трансляция обновлений : вызов страницы из Service Worker для информирования о важных обновлениях (например, о доступности новой версии веб-приложения).
- Двусторонняя связь : делегирование задачи сервисному работнику (например, большой загрузки) и информирование страницы о ходе выполнения.