До сих пор были лишь упоминания и крохотные фрагменты кода интерфейса 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;
}
});
Это игрушечный пример, который вы можете увидеть в действии самостоятельно , но он дает представление о том, на что способны сервисные работники. Приведенный выше код делает следующее:
- Проверьте свойство
destination
запроса, чтобы определить, является ли это запросом изображения. - Если изображение находится в кеше сервис-воркера, обслуживайте его оттуда. Если нет, извлеките изображение из сети, сохраните ответ в кеше и верните ответ сети.
- Все остальные запросы передаются через сервис-воркера без взаимодействия с кешем.
Объект 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
для запросов, которые вы хотите передать в сеть. Именно это происходит в демонстрации стратегии «Только кэширование» для запросов, которые не кэшируются предварительно.
Сначала кэшируйте, затем возвращайтесь к сети
В этой стратегии все становится немного сложнее. Для сопоставления запросов процесс выглядит следующим образом:
- Запрос попадает в кеш. Если ресурс находится в кеше, обслуживайте его оттуда.
- Если запроса нет в кеше, зайдите в сеть.
- После завершения сетевого запроса добавьте его в кеш, а затем верните ответ из сети.
Вот пример этой стратегии, который вы можете проверить в живой демо-версии :
// 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-кеш. Что еще более важно, любые кэшированные ресурсы будут доступны в автономном режиме.
Сначала сеть, возвращаясь к кешу
Если вы перевернете принцип «сначала кэш, потом сеть» с ног на голову, вы получите стратегию «сначала сеть, потом кэш», вот как это звучит:
- Сначала вы заходите в сеть за запросом и помещаете ответ в кеш.
- Если позже вы отключитесь от сети, вы вернетесь к последней версии этого ответа в кеше.
Эта стратегия отлично подходит для запросов 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, стратегия «сначала сеть, потом кэш» — это надежная стратегия, позволяющая достичь этой цели.
Устаревшие при повторной проверке
Из стратегий, которые мы рассмотрели до сих пор, стратегия «Устаревшие при повторной проверке» является самой сложной. В чем-то она похожа на две последние стратегии, но в этой процедуре приоритет отдается скорости доступа к ресурсу, а также поддерживается его актуальность в фоновом режиме. Эта стратегия выглядит примерно так:
- При первом запросе актива извлеките его из сети, поместите в кеш и верните ответ сети.
- При последующих запросах сначала обслуживайте актив из кэша, затем «в фоновом режиме» повторно запросите его из сети и обновите запись в кэше актива.
- Для запросов после этого вы получите последнюю версию, полученную из сети, которая была помещена в кэш на предыдущем шаге.
Это отличная стратегия для вещей, которые важно поддерживать в курсе событий, но не имеют решающего значения. Подумайте о таких вещах, как аватары для социальных сетей. Они обновляются, когда пользователи это делают, но последняя версия не является строго необходимой для каждого запроса.
// 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!