Стратегии кэширования Service Worker

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

Стратегия кэширования — это взаимодействие между событием fetch сервисного работника и интерфейсом Cache . От того, как написана стратегия кэширования, зависит; например, может быть предпочтительнее обрабатывать запросы на статические ресурсы иначе, чем на документы, и это влияет на то, как формируется стратегия кэширования.

Прежде чем мы перейдем к самим стратегиям, давайте поговорим о том, чем не является интерфейс Cache , чем он является, а также кратко рассмотрим некоторые методы, которые он предлагает для управления кэшами сервисных работников.

Интерфейс Cache и HTTP-кеш

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

  • Интерфейс Cache — это механизм кэширования, полностью отдельный от кэша HTTP.
  • Какую бы конфигурацию Cache-Control вы ни использовали для воздействия на HTTP-кеш, она не влияет на то, какие ресурсы хранятся в интерфейсе Cache .

Полезно представить кеши браузера как многоуровневые. Кэш HTTP — это низкоуровневый кеш, управляемый парами ключ-значение с директивами, выраженными в заголовках HTTP.

Напротив, интерфейс Cache представляет собой кеш высокого уровня, управляемый API JavaScript. Это обеспечивает большую гибкость, чем при использовании относительно упрощенных пар ключ-значение HTTP, и является половиной того, что делает возможными стратегии кэширования. Некоторые важные методы API для кэшей сервисных рабочих:

  • CacheStorage.open для создания нового экземпляра Cache .
  • Cache.add и Cache.put для хранения сетевых ответов в кеше сервисного работника.
  • Cache.match для поиска кэшированного ответа в экземпляре Cache .
  • Cache.delete чтобы удалить кэшированный ответ из экземпляра Cache .

Это лишь некоторые из них. Есть и другие полезные методы, но это основные, которые вы увидите позже в этом руководстве.

Скромное событие fetch

Другая половина стратегии кэширования — это событие fetch сервисного работника. До сих пор в этой документации вы немного слышали о «перехвате сетевых запросов», и это происходит в событии fetch внутри сервис-воркера:

// Establish a cache name const cacheName = 'MyFancyCacheName_v1';  self.addEventListener('install', (event) => {   event.waitUntil(caches.open(cacheName)); });  self.addEventListener('fetch', async (event) => {   // Is this a request for an image?   if (event.request.destination === 'image') {     // Open the cache     event.respondWith(caches.open(cacheName).then((cache) => {       // Respond with the image from the cache or from the network       return cache.match(event.request).then((cachedResponse) => {         return cachedResponse || fetch(event.request.url).then((fetchedResponse) => {           // Add the network response to the cache for future visits.           // Note: we need to make a copy of the response to save it in           // the cache and use the original as the request response.           cache.put(event.request, fetchedResponse.clone());            // Return the network response           return fetchedResponse;         });       });     }));   } else {     return;   } }); 

Это игрушечный пример, который вы можете увидеть в действии самостоятельно , но он дает представление о том, на что способны сервисные работники. Приведенный выше код делает следующее:

  1. Проверьте свойство destination запроса, чтобы определить, является ли это запросом изображения.
  2. Если изображение находится в кеше сервис-воркера, обслуживайте его оттуда. Если нет, извлеките изображение из сети, сохраните ответ в кеше и верните ответ сети.
  3. Все остальные запросы передаются через сервис-воркера без взаимодействия с кешем.

Объект event выборки содержит свойство request , которое содержит некоторую полезную информацию, которая поможет вам определить тип каждого запроса:

  • url — URL-адрес сетевого запроса, который в данный момент обрабатывается событием fetch .
  • method , который является методом запроса (например, GET или POST ).
  • mode , который описывает режим запроса. Значение 'navigate' часто используется, чтобы отличать запросы HTML-документов от других запросов.
  • destination , который описывает тип запрашиваемого контента таким образом, чтобы избежать использования расширения файла запрошенного ресурса.

Еще раз: асинхронность — это название игры. Вы помните, что событие install предлагает метод event.waitUntil , который принимает обещание и ожидает его разрешения, прежде чем продолжить активацию. Событие fetch предлагает аналогичный метод event.respondWith , который можно использовать для возврата результата запроса асинхронной fetch или ответа, возвращаемого методом match интерфейса Cache .

Стратегии кэширования

Теперь, когда вы немного знакомы с экземплярами Cache и обработчиком событий fetch , вы готовы погрузиться в некоторые стратегии кэширования сервис-воркеров. Хотя возможности практически безграничны, в этом руководстве будут рассмотрены стратегии, поставляемые с Workbox, так что вы сможете получить представление о том, что происходит внутри Workbox.

Только кэш

Показывает поток от страницы к сервисному работнику и к кэшу.

Начнем с простой стратегии кэширования, которую мы назовем «Только кэш». Дело в том, что когда сервис-воркер контролирует страницу, соответствующие запросы будут поступать только в кеш. Это означает, что любые кэшированные ресурсы необходимо будет предварительно кэшировать, чтобы они были доступны для работы шаблона, и что эти активы никогда не будут обновляться в кеше до тех пор, пока не будет обновлен сервисный работник.

// Establish a cache name const cacheName = 'MyFancyCacheName_v1';  // Assets to precache const precachedAssets = [   '/possum1.jpg',   '/possum2.jpg',   '/possum3.jpg',   '/possum4.jpg' ];  self.addEventListener('install', (event) => {   // Precache assets on install   event.waitUntil(caches.open(cacheName).then((cache) => {     return cache.addAll(precachedAssets);   })); });  self.addEventListener('fetch', (event) => {   // Is this one of our precached assets?   const url = new URL(event.request.url);   const isPrecachedRequest = precachedAssets.includes(url.pathname);    if (isPrecachedRequest) {     // Grab the precached asset from the cache     event.respondWith(caches.open(cacheName).then((cache) => {       return cache.match(event.request.url);     }));   } else {     // Go to the network     return;   } }); 

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

Только сеть

Показывает поток от страницы к сервисному работнику и сети.

Противоположностью «Только кэша» является «Только сеть», когда запрос передается через сервис-воркера в сеть без какого-либо взаимодействия с кэшем сервис-воркера. Это хорошая стратегия для обеспечения актуальности контента (например, разметка), но компромиссом является то, что она никогда не будет работать, когда пользователь не в сети.

Обеспечение прохождения запроса в сеть означает, что вы не вызываете event.respondWith для соответствующего запроса. Если вы хотите быть явным, вы можете указать пустой return; в обратном вызове события fetch для запросов, которые вы хотите передать в сеть. Именно это происходит в демонстрации стратегии «Только кэширование» для запросов, которые не кэшируются предварительно.

Сначала кэшируйте, затем возвращайтесь к сети

Показывает поток со страницы к сервисному работнику, в кеш, а затем в сеть, если он не в кеше.

В этой стратегии все становится немного сложнее. Для сопоставления запросов процесс выглядит следующим образом:

  1. Запрос попадает в кеш. Если ресурс находится в кеше, обслуживайте его оттуда.
  2. Если запроса нет в кеше, зайдите в сеть.
  3. После завершения сетевого запроса добавьте его в кеш, а затем верните ответ из сети.

Вот пример этой стратегии, который вы можете проверить в живой демо-версии :

// Establish a cache name const cacheName = 'MyFancyCacheName_v1';  self.addEventListener('fetch', (event) => {   // Check if this is a request for an image   if (event.request.destination === 'image') {     event.respondWith(caches.open(cacheName).then((cache) => {       // Go to the cache first       return cache.match(event.request.url).then((cachedResponse) => {         // Return a cached response if we have one         if (cachedResponse) {           return cachedResponse;         }          // Otherwise, hit the network         return fetch(event.request).then((fetchedResponse) => {           // Add the network response to the cache for later visits           cache.put(event.request, fetchedResponse.clone());            // Return the network response           return fetchedResponse;         });       });     }));   } else {     return;   } }); 

Хотя этот пример охватывает только изображения, это отличная стратегия, которую можно применить ко всем статическим ресурсам (таким как CSS, JavaScript, изображения и шрифты), особенно к хеш-версиям. Он обеспечивает повышение скорости работы неизменяемых ресурсов за счет обхода любых проверок актуальности контента на сервере, который может запустить HTTP-кеш. Что еще более важно, любые кэшированные ресурсы будут доступны в автономном режиме.

Сначала сеть, возвращаясь к кешу

Показывает поток от страницы к сервисному работнику, в сеть, а затем в кэш, если сеть недоступна.

Если вы перевернете принцип «сначала кэш, потом сеть» с ног на голову, вы получите стратегию «сначала сеть, потом кэш», вот как это звучит:

  1. Сначала вы заходите в сеть за запросом и помещаете ответ в кеш.
  2. Если позже вы отключитесь от сети, вы вернетесь к последней версии этого ответа в кеше.

Эта стратегия отлично подходит для запросов HTML или API, когда в режиме онлайн вам нужна самая последняя версия ресурса, но вы хотите предоставить автономный доступ к самой последней доступной версии. Вот как это может выглядеть применительно к запросам HTML:

// Establish a cache name const cacheName = 'MyFancyCacheName_v1';  self.addEventListener('fetch', (event) => {   // Check if this is a navigation request   if (event.request.mode === 'navigate') {     // Open the cache     event.respondWith(caches.open(cacheName).then((cache) => {       // Go to the network first       return fetch(event.request.url).then((fetchedResponse) => {         cache.put(event.request, fetchedResponse.clone());          return fetchedResponse;       }).catch(() => {         // If the network is unavailable, get         return cache.match(event.request.url);       });     }));   } else {     return;   } }); 

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

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

Устаревшие при повторной проверке

Показывает поток от страницы к сервисному работнику, к кэшу, а затем из сети в кеш.

Из стратегий, которые мы рассмотрели до сих пор, стратегия «Устаревшие при повторной проверке» является самой сложной. В чем-то она похожа на две последние стратегии, но в этой процедуре приоритет отдается скорости доступа к ресурсу, а также поддерживается его актуальность в фоновом режиме. Эта стратегия выглядит примерно так:

  1. При первом запросе актива извлеките его из сети, поместите в кеш и верните ответ сети.
  2. При последующих запросах сначала обслуживайте актив из кэша, затем «в фоновом режиме» повторно запросите его из сети и обновите запись в кэше актива.
  3. Для запросов после этого вы получите последнюю версию, полученную из сети, которая была помещена в кэш на предыдущем шаге.

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

// Establish a cache name const cacheName = 'MyFancyCacheName_v1';  self.addEventListener('fetch', (event) => {   if (event.request.destination === 'image') {     event.respondWith(caches.open(cacheName).then((cache) => {       return cache.match(event.request).then((cachedResponse) => {         const fetchedResponse = fetch(event.request).then((networkResponse) => {           cache.put(event.request, networkResponse.clone());            return networkResponse;         });          return cachedResponse || fetchedResponse;       });     }));   } else {     return;   } }); 

Вы можете увидеть это в действии в еще одной живой демонстрации , особенно если вы обратите внимание на вкладку сети в инструментах разработчика вашего браузера и на ее средство просмотра CacheStorage (если в инструментах разработчика вашего браузера есть такой инструмент).

Вперед в Workbox!

В этом документе завершается наш обзор API сервис-воркеров, а также связанных с ними API, что означает, что вы узнали достаточно о том, как напрямую использовать сервис-воркеров, чтобы начать работать с Workbox!