Стратегии кэширования сервисных работников,Стратегии кэширования сервисных работников

До сих пор были лишь упоминания и крохотные фрагменты кода интерфейса 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!