API кэша: краткое руководство

Узнайте, как использовать API кэширования, чтобы сделать данные вашего приложения доступными в автономном режиме.

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

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

Где это доступно?

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

const cacheAvailable = 'caches' in self;

Browser Support

  • Хром: 40.
  • Край: 16.
  • Firefox: 41.
  • Сафари: 11.1.

Source

Доступ к API кэша можно получить из окна, iframe, воркера или сервисного воркера.

Что можно хранить

Кэши хранят только пары объектов Request и Response , представляющие HTTP-запросы и ответы соответственно. Однако запросы и ответы могут содержать любые виды данных, которые могут передаваться по HTTP.

Сколько можно хранить?

Короче говоря, много , как минимум пара сотен мегабайт, а потенциально сотни гигабайт или больше. Реализации браузеров различаются, но объем доступного хранилища обычно зависит от объема хранилища, доступного на устройстве.

Создание и открытие кэша

Чтобы открыть кэш, используйте метод caches.open(name) , передав имя кэша в качестве единственного параметра. Если именованный кэш не существует, он создается. Этот метод возвращает Promise , который разрешается с помощью объекта Cache .

const cache = await caches.open('my-cache');
// do something with cache...

Добавление в кэш

Есть три способа добавить элемент в кэш - add , addAll и put . Все три метода возвращают Promise .

cache.add

Во-первых, есть cache.add() . Он принимает один параметр, либо Request , либо URL ( string ). Он делает запрос к сети и сохраняет ответ в кэше. Если выборка не удалась или если код статуса ответа не находится в диапазоне 200, то ничего не сохраняется, и Promise отклоняется. Обратите внимание, что запросы кросс-источника, не находящиеся в режиме CORS, не могут быть сохранены, поскольку они возвращают status 0 . Такие запросы можно сохранить только с помощью put .

// Retreive data.json from the server and store the response.
cache.add(new Request('/data.json'));

// Retreive data.json from the server and store the response.
cache.add('/data.json');

cache.addAll

Далее идет cache.addAll() . Он работает аналогично add() , но принимает массив объектов Request или URL-адресов ( string s). Это работает аналогично вызову cache.add для каждого отдельного запроса, за исключением того, что Promise отклоняется, если какой-либо отдельный запрос не кэширован.

const urls = ['/weather/today.json', '/weather/tomorrow.json'];
cache.addAll(urls);

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

cache.put

Наконец, есть cache.put() , который позволяет вам хранить либо ответ из сети, либо создавать и хранить свой собственный Response . Он принимает два параметра. Первый может быть либо объектом Request , либо URL ( string ). Второй должен быть Response , либо из сети, либо сгенерированным вашим кодом.

// Retrieve data.json from the server and store the response.
cache.put('/data.json');

// Create a new entry for test.json and store the newly created response.
cache.put('/test.json', new Response('{"foo": "bar"}'));

// Retrieve data.json from the 3rd party site and store the response.
cache.put('https://example.com/data.json');

Метод put() более разрешителен, чем add() или addAll() , и позволяет сохранять ответы, не относящиеся к CORS, или другие ответы, код статуса которых не находится в диапазоне 200. Он перезапишет все предыдущие ответы для того же запроса.

Создание объектов запроса

Создайте объект 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 в противном случае.

Чтобы определить, совпадают ли два Requests , браузер использует не только URL. Два запроса считаются разными, если у них разные строки запроса, заголовки Vary или методы HTTP ( GET , POST , PUT и т. д.).

Вы можете игнорировать некоторые или все эти вещи, передав объект параметров в качестве второго параметра.

const options = {
  ignoreSearch: true,
  ignoreMethod: true,
  ignoreVary: true
};

const response = await cache.match(request, options);
// do something with the response

Если совпало более одного кэшированного запроса, то возвращается тот, который был создан первым. Если вы хотите получить все совпадающие ответы, вы можете использовать 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() для каждого кэша.

Идет поиск

API кэша не предоставляет способа поиска запросов или ответов, за исключением сопоставления записей с объектом Response . Однако вы можете реализовать свой собственный поиск с помощью фильтрации или создания индекса.

Фильтрация

Один из способов реализовать собственный поиск — это перебрать все записи и отфильтровать их по нужным. Допустим, вы хотите найти все элементы, URL-адреса которых заканчиваются на .png .

async function findImages() {
  // Get a list of all of the caches for this origin
  const cacheNames = await caches.keys();
  const result = [];

  for (const name of cacheNames) {
    // Open the cache
    const cache = await caches.open(name);

    // Get a list of entries. Each item is a Request object
    for (const request of await cache.keys()) {
      // If the request URL matches, add the response to the result
      if (request.url.endsWith('.png')) {
        result.push(await cache.match(request));
      }
    }
  }

  return result;
}

Таким образом, вы можете использовать любое свойство объектов Request и Response для фильтрации записей. Обратите внимание, что это медленно, если вы ищете по большим наборам данных.

Создание индекса

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

Если вы сохраните URL-адрес Request вместе с доступными для поиска свойствами, то вы сможете легко получить правильную запись кэша после выполнения поиска.

Удаление элемента

Чтобы удалить элемент из кэша:

cache.delete(request);

Где request может быть Request или строкой URL. Этот метод также принимает тот же объект options, что и cache.match , что позволяет удалять несколько пар Request / Response для одного и того же URL.

cache.delete('/example/file.txt', {ignoreVary: true, ignoreSearch: true});

Удаление кэша

Чтобы удалить кэш, вызовите caches.delete(name) . Эта функция возвращает Promise , который разрешается как true если кэш существовал и был удален, или false в противном случае.

Спасибо

Выражаю благодарность Мэту Скейлсу, написавшему оригинальную версию этой статьи, которая впервые появилась на WebFundamentals.