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.