Cache API: краткое руководство
Узнайте, как сделать данные приложения доступными в офлайн-режиме при помощи Cache API.
Cache API — это система для хранения и получения доступа к сетевым запросам и соответствующим им ответам. Это могут быть как обычные запросы и ответы, создаваемые в ходе работы приложения, так и специальные запросы и ответы, создаваемые исключительно с целью сохранить данные для дальнейшего использования.
Cache API был создан, чтобы позволить сервис-воркерам кешировать сетевые запросы и тем самым обеспечивать быстрое время ответа вне зависимости от скорости и доступности сетевого подключения. Однако этот API можно использовать в качестве механизма хранения данных общего назначения.
Где он доступен? #
Cache API доступен во всех современных браузерах. Доступ к нему осуществляется через глобальное свойство caches
, поэтому проверить наличие API очень просто:
const cacheAvailable = 'caches' in self;
Доступ к Cache API можно получить из окна, элемента iframe, веб-воркера или сервис-воркера.
Какие данные в нем можно хранить #
В кеше могут храниться только пары объектов Request
и Response
, представляющих HTTP-запросы и ответы соответственно. Однако запросы и ответы могут содержать любые данные, которые можно передавать по HTTP.
Какой объем данных можно хранить? #
Большой: как минимум пару сотен мегабайт, а в теории — сотни гигабайт или больше. Реализации в браузерах различаются, однако объем, доступный для хранения данных, обычно зависит от объема памяти устройства.
Создание и открытие кеша #
Чтобы открыть кеш, используйте метод caches.open(name)
, указав в качестве единственного параметра название кеша. Если кеша с таким названием не существует, он будет создан. Метод возвращает обещание (Promise
), которое разрешается в объект Cache
.
const cache = await caches.open('my-cache');
// далее выполняем операции с объектом cache...
Добавление записи в кеш #
Для добавления записи в кеш существуют три метода: add
, addAll
и put
. Все три метода возвращают Promise
.
cache.add
#
Первый метод — это cache.add()
. Он принимает один параметр: либо объект Request
, либо URL-адрес (в виде строки). Метод инициирует сетевой запрос и сохраняет в кеше ответ на него. Если загрузка завершается неудачно или код ответа лежит за пределами диапазона 200, то ответ не сохраняется, а Promise
будет отклонен. Обратите внимание, что запросы между различными источниками, выполненные не в режиме CORS, не будут сохранены, поскольку возвращают status
0
. Такие запросы можно сохранять только при помощи метода put
.
// Загружаем с сервера data.json и сохраняем ответ.
cache.add(new Request('/data.json'));
// Загружаем с сервера data.json и сохраняем ответ.
cache.add('/data.json');
cache.addAll
#
Следующий метод — cache.addAll()
. Он работает аналогично add()
, но принимает массив объектов Request
или URL-адресов (в виде строк). Вызов этого метода идентичен вызову cache.add
для каждого запроса по отдельности, с тем исключением, что Promise
будет отклонен в случае неудачного завершения любого из запросов.
const urls = ['/weather/today.json', '/weather/tomorrow.json'];
cache.addAll(urls);
В каждом из этих случаев новая запись перезаписывает предыдущую совпадающую с ней запись. Критерии совпадения описываются в разделе об извлечении записей ниже.
cache.put
#
Последний метод — cache.put()
, при помощи которого можно как сохранить ответ из сети, так и создать и сохранить свой собственный объект Response
. Метод принимает два параметра. Первый параметр — либо объект Request
, либо URL-адрес (в виде строки). Второй параметр — объект Response
(либо полученный из сети, либо сгенерированный вашим собственным кодом).
// Получаем data.json с сервера и сохраняем ответ.
cache.put('/data.json');
// Создаем новую запись для test.json и сохраняем свежесозданный ответ.
cache.put('/test.json', new Response('{"foo": "bar"}'));
// Скачиваем data.json со стороннего сайта и сохраняем ответ.
cache.put('https://example.com/data.json');
Метод put()
имеет менее строгие ограничения, чем add()
или addAll()
, и позволяет сохранять ответы, полученные не в режиме CORS, а также иные ответы с кодом состояния за пределами диапазона 200. Он перезаписывает любые предыдущие ответы на аналогичный запрос.
Создание объектов Request #
При создании объекта Request
в качестве аргумента указывается URL-адрес сохраняемого ресурса:
const request = new Request('/my-data-store/item-id');
Работа с объектами Response #
Конструктор объекта Response
принимает множество типов данных, включая Blob
, ArrayBuffer
, FormData
и строки.
const imageBlob = new Blob([data], {type: 'image/jpeg'});
const imageResponse = new Response(imageBlob);
const stringResponse = new Response('Hello world');
MIME-тип объекта Response
устанавливается путем установки соответствующего заголовка.
const options = {
headers: {
'Content-Type': 'application/json'
}
}
const jsonResponse = new Response('{}', options);
Если вы извлекли объект Response
и хотите получить доступ к его телу, для этого существует ряд вспомогательных методов. Каждый метод возвращает Promise
, который разрешается в значение соответствующего типа.
Метод | Описание |
---|---|
arrayBuffer | Возвращает объект ArrayBuffer с телом, сериализованным в байты. |
blob | Возвращает объект Blob . Если объект Response был создан с объектом Blob , то новый объект Blob будет иметь тот же тип. В противном случае используется заголовок Content-Type объекта Response . |
text | Интерпретирует байты тела как строку в кодировке UTF-8. |
json | Интерпретирует байты тела как строку в кодировке UTF-8, а затем пытается обработать ее как JSON. Возвращает получившийся в результате объект или выдает ошибку TypeError , если строку не удается обработать как JSON. |
formData | Интерпретирует байты тела как HTML-форму, закодированную в формате multipart/form-data или application/x-www-form-urlencoded . Возвращает объект FormData или, если данные не удается обработать, выдает ошибку TypeError . |
body | Возвращает объект ReadableStream для чтения данных тела. |
Пример:
const response = new Response('Hello world');
const buffer = await response.arrayBuffer();
console.log(new Uint8Array(buffer));
// Uint8Array(11) [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]
Извлечение записей из кеша #
Для поиска записи в кеше можно использовать метод match
.
const response = await cache.match(request);
console.log(request, response);
Если параметр request
является строкой, браузер преобразует его в объект Request
при помощи вызова new Request(request)
. Функция возвращает Promise
, который в случае обнаружения подходящей записи разрешается в объект Response
, а в противном случае — в undefined
.
При сравнении двух объектов Request
браузер учитывает не только URL-адрес. Объекты Request считаются несовпадающими, если у них различаются строки запроса, заголовки Vary
или методы HTTP (GET
, POST
, PUT
и т. д.).
Некоторые из этих параметров можно игнорировать, если передать в качестве второго аргумента объект options с соответствующими опциями.
const options = {
ignoreSearch: true,
ignoreMethod: true,
ignoreVary: true
};
const response = await cache.match(request, options);
// обработка ответа
Если найдено более одного кешированного запроса, то будет возвращен запрос, который был создан раньше всего. Если вам нужно извлечь все запросы, соответствующие критериям, используйте метод cache.matchAll()
.
const options = {
ignoreSearch: true,
ignoreMethod: true,
ignoreVary: true
};
const responses = await cache.matchAll(request, options);
console.log(`There are ${responses.length} matching responses.`);
Чтобы сократить объем кода, для поиска по всем кешам сразу можно использовать caches.match()
, вместо того чтобы вызывать cache.match()
для каждого отдельного кеша.
Поиск #
Cache API не позволяет искать запросы или ответы каким-либо образом, кроме сравнения записей с объектом Response
. Однако возможность поиска можно реализовать самостоятельно, используя фильтрацию или индексирование.
Фильтрация #
Один из способов поиска заключается в обходе всех записей подряд и фильтрации их согласно установленным критериям. Предположим, вам нужно найти все записи, чей URL-адрес заканчивается на .png
.
async function findImages() {
// Получаем список всех кешей для текущего источника
const cacheNames = await caches.keys();
const result = [];
for (const name of cacheNames) {
// Открываем кеш
const cache = await caches.open(name);
// Получаем список записей. Каждая запись — объект Request
for (const request of await cache.keys()) {
// Если URL-адрес запроса совпадает, добавляем ответ к результату
if (request.url.endsWith('.png')) {
result.push(await cache.match(request));
}
}
}
return result;
}
Таким образом вы можете использовать для фильтрации записей любое свойство объектов Request
и Response
. Обратите внимание, что на больших массивах данных поиск будет медленным.
Создание индекса #
Еще один способ реализации поиска заключается в том, чтобы создать отдельный индекс с записями, по которым можно выполнять поиск, и использовать IndexedDB для его хранения. Поскольку API IndexedDB предназначен специально для таких целей, этот подход позволяет обеспечить гораздо более высокую производительность при большом количестве записей.
Если URL-адрес объекта Request
будет храниться вместе со свойствами, используемыми для поиска, вы сможете легко находить нужную запись в кеше и извлекать ее.
Удаление записи #
Удалить запись из кеша можно следующим образом:
cache.delete(request);
Параметр request — это либо объект Request
, либо URL-адрес в виде строки. Этот метод также принимает объект option с теми же параметрами, что и для метода cache.match
, благодаря чему его можно использовать для удаления сразу нескольких пар Request
/Response
, соответствующих одному и тому же URL-адресу.
cache.delete('/example/file.txt', {ignoreVary: true, ignoreSearch: true});
Удаление кеша #
Чтобы удалить кеш, используйте caches.delete(name)
. Функция возвращает Promise
, который разрешается в true
, если кеш существовал и был удален, или в false
в противном случае.
Благодарности #
Спасибо Мэту Скейлсу за написание оригинальной версии этой статьи, впервые опубликованной на WebFundamentals.