---
layou: post
title: 'Cache API: краткое руководство'
subhead: Узнайте, как сделать данные приложения доступными в офлайн-режиме при помощи Cache API.
authors:
  - petelepage
description: Узнайте, как сделать данные приложения доступными в офлайн-режиме при помощи Cache API.
date: 2017-10-03
updated: 2020-04-27
tags:
  - blog
  - progressive-web-apps
feedback:
  - api
---

[Cache API](https://developer.mozilla.org/docs/Web/API/Cache) — это система для хранения и получения доступа к сетевым запросам и соответствующим им ответам. Это могут быть как обычные запросы и ответы, создаваемые в ходе работы приложения, так и специальные запросы и ответы, создаваемые исключительно с целью сохранить данные для дальнейшего использования.

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

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

Cache API доступен во [всех современных браузерах](https://caniuse.com/#feat=mdn-api_cache). Доступ к нему осуществляется через глобальное свойство `caches`, поэтому проверить наличие API очень просто:

```js
const cacheAvailable = 'caches' in self;
```

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

## Какие данные в нем можно хранить

В кеше могут храниться только пары объектов [`Request`](https://developer.mozilla.org/docs/Web/API/Request) и [`Response`](https://developer.mozilla.org/docs/Web/API/Response), представляющих HTTP-запросы и ответы соответственно. Однако запросы и ответы могут содержать любые данные, которые можно передавать по HTTP.

### Какой объем данных можно хранить?

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

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

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

```js
const cache = await caches.open('my-cache');
// далее выполняем операции с объектом cache...
```

## Добавление записи в кеш

Для добавления записи в кеш существуют три метода: `add`, `addAll` и `put`. Все три метода возвращают `Promise`.

### `cache.add`

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

```js
// Загружаем с сервера data.json и сохраняем ответ.
cache.add(new Request('/data.json'));

// Загружаем с сервера data.json и сохраняем ответ.
cache.add('/data.json');
```

### `cache.addAll`

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

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

В каждом из этих случаев новая запись перезаписывает предыдущую совпадающую с ней запись. Критерии совпадения описываются в разделе об [извлечении записей](#retrieving) ниже.

### `cache.put`

Последний метод — `cache.put()`, при помощи которого можно как сохранить ответ из сети, так и создать и сохранить свой собственный объект `Response`. Метод принимает два параметра. Первый параметр — либо объект `Request`, либо URL-адрес (в виде строки). Второй параметр — объект `Response` (либо полученный из сети, либо сгенерированный вашим собственным кодом).

```js
// Получаем 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-адрес сохраняемого ресурса:

```js
const request = new Request('/my-data-store/item-id');
```

#### Работа с объектами Response

Конструктор объекта `Response` принимает множество типов данных, включая `Blob`, `ArrayBuffer`, `FormData` и строки.

```js
const imageBlob = new Blob([data], {type: 'image/jpeg'});
const imageResponse = new Response(imageBlob);
const stringResponse = new Response('Hello world');
```

MIME-тип объекта `Response` устанавливается путем установки соответствующего заголовка.

```js
  const options = {
    headers: {
      'Content-Type': 'application/json'
    }
  }
  const jsonResponse = new Response('{}', options);
```

Если вы извлекли объект `Response` и хотите получить доступ к его телу, для этого существует ряд вспомогательных методов. Каждый метод возвращает `Promise`, который разрешается в значение соответствующего типа.

<table>
  <thead>
    <th>Метод</th>
    <th>Описание</th>
  </thead>
  <tbody>
    <tr>
      <td><code>arrayBuffer</code></td>
      <td>Возвращает объект <code>ArrayBuffer</code> с телом, сериализованным в байты.</td>
    </tr>
    <tr>
      <td><code>blob</code></td>
      <td>Возвращает объект <code>Blob</code>. Если объект <code>Response</code> был создан с объектом <code>Blob</code>, то новый объект <code>Blob</code> будет иметь тот же тип. В противном случае используется заголовок <code>Content-Type</code> объекта <code>Response</code>.</td>
    </tr>
    <tr>
      <td><code>text</code></td>
      <td>Интерпретирует байты тела как строку в кодировке UTF-8.</td>
    </tr>
    <tr>
      <td><code>json</code></td>
      <td>Интерпретирует байты тела как строку в кодировке UTF-8, а затем пытается обработать ее как JSON. Возвращает получившийся в результате объект или выдает ошибку <code>TypeError</code>, если строку не удается обработать как JSON.</td>
    </tr>
    <tr>
      <td><code>formData</code></td>
      <td>Интерпретирует байты тела как HTML-форму, закодированную в формате <code>multipart/form-data</code> или <code>application/x-www-form-urlencoded</code>. Возвращает объект <a href="https://developer.mozilla.org/docs/Web/API/FormData">FormData</a> или, если данные не удается обработать, выдает ошибку <code>TypeError</code>.</td>
    </tr>
    <tr>
      <td><code>body</code></td>
      <td>Возвращает объект <a href="https://developer.mozilla.org/docs/Web/API/ReadableStream">ReadableStream</a> для чтения данных тела.</td>
    </tr>
  </tbody>
</table>

Пример:

```js
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]
```

## Извлечение записей из кеша {: #retrieving }

Для поиска записи в кеше можно использовать метод `match`.

```js
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 с соответствующими опциями.

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

const response = await cache.match(request, options);
// обработка ответа
```

Если найдено более одного кешированного запроса, то будет возвращен запрос, который был создан раньше всего. Если вам нужно извлечь *все* запросы, соответствующие критериям, используйте метод `cache.matchAll()`.

```js
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`.

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

## Удаление записи

Удалить запись из кеша можно следующим образом:

```js
cache.delete(request);
```

Параметр request — это либо объект `Request`, либо URL-адрес в виде строки. Этот метод также принимает объект option с теми же параметрами, что и для метода `cache.match`, благодаря чему его можно использовать для удаления сразу нескольких пар `Request`/`Response`, соответствующих одному и тому же URL-адресу.

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

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

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

## Благодарности

Спасибо Мэту Скейлсу за написание оригинальной версии этой статьи, впервые опубликованной на WebFundamentals.
