Руководство по императивному кэшированию

Эндрю Гуан
Andrew Guan

Некоторым веб-сайтам может потребоваться связаться с сервисным работником без необходимости информирования о результате. Вот некоторые примеры:

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

Делегирование этих типов некритических задач сервисному работнику дает преимущество в освобождении основного потока для более эффективной обработки более насущных задач, таких как реагирование на взаимодействия с пользователем.

Схема страницы, запрашивающей ресурсы для кэширования сервисному работнику.

В этом руководстве мы рассмотрим, как реализовать метод односторонней связи между страницей и сервис-воркером с помощью стандартных API-интерфейсов браузера и библиотеки Workbox. Мы будем называть такие варианты использования императивным кэшированием .

Производственный корпус

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

Логотип 1-800 цветов.

Они используют смешанный подход, чтобы решить, какие элементы следует выбирать предварительно:

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

Они используют Cache API для хранения ответов JSON:

Логотип 1-800 цветов.
Предварительная выборка данных о товарах в формате JSON со страниц со списком товаров на сайте 1-800Flowers.com.

Когда пользователь нажимает на элемент, связанные с ним данные JSON могут быть извлечены из кеша без необходимости обращения к сети, что ускоряет навигацию.

Использование Workbox

Workbox предоставляет простой способ отправки сообщений сервисному работнику с помощью пакета workbox-window — набора модулей, предназначенных для запуска в контексте окна. Они являются дополнением к другим пакетам Workbox, которые запускаются в сервис-воркере.

Чтобы передать страницу сервисному работнику, сначала получите ссылку на объект Workbox зарегистрированного сервисного работника:

const wb = new Workbox('/sw.js');
wb.register();

Затем вы можете напрямую отправить сообщение декларативно, без необходимости получать регистрацию, проверять активацию или думать о базовом коммуникационном API:

wb.messageSW({"type": "PREFETCH", "payload": {"urls": ["/data1.json", "data2.json"]}}); });

Сервисный работник реализует обработчик message для прослушивания этих сообщений. При желании он может вернуть ответ, хотя в подобных случаях в этом нет необходимости:

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PREFETCH') {
    // do something
  }
});

Использование API браузера

Если библиотеки Workbox недостаточно для ваших нужд, вот как вы можете реализовать взаимодействие между окном и сервисным работником, используя API-интерфейсы браузера.

API postMessage можно использовать для установления механизма односторонней связи между страницей и сервис-воркером.

Страница вызывает postMessage() в интерфейсе сервис-воркера:

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 пытается выполнить предварительное кэширование, странице не нужно знать, добился ли сервис-воркер успеха. Если это так, то пользователь получит более быструю навигацию. Если это не так, страница все равно должна перейти на новую страницу. Просто это займет немного больше времени.

Простой пример предварительной выборки

Одним из наиболее распространенных применений императивного кэширования является предварительная выборка , то есть получение ресурсов для данного URL-адреса до того, как пользователь перейдет к нему, чтобы ускорить навигацию.

Существуют разные способы реализации предварительной загрузки на сайтах:

Для относительно простых сценариев предварительной загрузки, таких как предварительная загрузка документов или определенных ресурсов (JS, CSS и т. д.), эти методы являются лучшим подходом.

Если требуется дополнительная логика, например анализ ресурса предварительной выборки (файла или страницы JSON) для получения его внутренних URL-адресов, целесообразнее полностью делегировать эту задачу сервисному работнику.

Делегирование этих типов операций сервисному работнику имеет следующие преимущества:

  • Перенос тяжелой работы по выборке и пост-выборке (которая будет представлена ​​позже) на вторичный поток. Делая это, он освобождает основной поток для выполнения более важных задач, таких как реагирование на взаимодействия с пользователем.
  • Разрешение нескольким клиентам (например, вкладкам) повторно использовать общую функциональность и даже одновременный вызов службы без блокировки основного потока.

Предварительная загрузка страниц с подробными сведениями о продукте

Сначала используйте postMessage() в интерфейсе сервис-воркера и передайте в кэш массив 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 ). В подобных случаях вы можете переопределить это поведение, сохранив полученный ресурс в кеше сервисного работника. Это дает дополнительное преимущество, позволяя обслуживать файл в автономных сценариях.

За пределами данных 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. Но прелесть использования сервисного работника для предварительной выборки состоит в том, что она может завершиться неудачей без особых последствий для страницы и основного потока. У вас также может быть более сложная логика постобработки предварительно загруженного контента, что делает его более гибким и независимым от данных, которые он обрабатывает. Небо это предел.

Заключение

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

Дополнительные шаблоны взаимодействия между страницами и сервисными работниками см. на странице:

  • Широковещательные обновления : вызов страницы из сервисного работника, чтобы сообщить о важных обновлениях (например, доступна новая версия веб-приложения).
  • Двусторонняя связь : делегирование задачи сервисному работнику (например, тяжелая загрузка) и информирование страницы о ходе выполнения.