Cache API — это система для хранения и извлечения сетевых запросов и соответствующих ответов. Это могут быть обычные запросы и ответы, созданные в ходе работы вашего приложения, или это могут быть запросы созданы исключительно для хранения некоторых данных в кеше.
Cache API был создан для того, чтобы Service Workers могли кэшировать сетевые запросы, чтобы они могли предоставлять соответствующие ответы даже в автономном режиме. Тем не менее, это API также может быть использовано в качестве общего механизма хранения.
API в настоящее время доступно в Chrome, Opera и Firefox. И Edge, и Safari помечено «В разработке».
API предоставляется через свойство global caches, так что вы можете проверить наличие API с помощью простого обнаружения функции:
const cacheAvailable = 'caches' in self;
К API можно получить доступ из window, iframe, worker или service worker.
Кэш хранит только пары объектов Request и Response, представляющих HTTP-запросы и ответы соответственно. Однако запросы и ответы могут содержать любые данные, которые можно передавать по HTTP.
Можно создать объект Request, используя URL для сохранения в кеше:
const request = new Request('/images/sample1.jpg');
Конструктор объекта Response принимает разные типы данных, включая Blobs, ArrayBuffers, объекты 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 и хотите получить доступ к его body, вы можете использовать несколько вспомогательных методов. Каждый возвращает Promise, которое разрешается со значением другого типа.
Method | Description |
---|---|
arrayBuffer | Возвращает ArrayBuffer, содержащий body, сериализованное в байты. |
blob | Возвращает Blob Если Response был создан с помощью Blob, то этот новый Blob имеет тот же тип. В противном случае используется Content-Type Response. |
text | Интерпретирует байты body как строку в кодировке UTF-8. |
json | Интерпретирует байты body как строку в кодировке UTF-8, а затем пытается проанализировать ее как JSON. Возвращает результирующий объект или выдает ошибку TypeError, если строка не может быть преобразована в JSON. |
formData | Интерпретирует байты тела как форму HTML, закодированную как «multipart/form-data» или «application/x-www-form-urlencoded». Возвращает объект FormData или выдает ошибку TypeError, если данные не могут быть проанализированы. |
body | Возвращает ReadableStream для данных body. |
Для примера
const response = new Response('Hello world'); response.arrayBuffer().then((buffer) => { console.log(new Uint8Array(buffer)); // Uint8Array(11) [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100] });
Чтобы открыть кеш, используйте метод caches.open(name), передавая имя кеша в качестве единственного параметра. Если именованный кеш не существует, он создается. Этот метод возвращает Promise, который разрешается с помощью объекта Cache.
caches.open('my-cache').then((cache) => { // do something with cache... });
Чтобы найти элемент в кеше, используется метод match.
cache.match(request).then((response) => console.log(request, response));
Если запрос является строкой, он сначала преобразуется в запрос путем вызова new Request(request). Функция возвращает Promise, который разрешается в Response, если найдена соответствующая запись, или undefined в противном случае.
Чтобы определить, совпадают ли два запроса, используется не только URL-адрес. Два запроса считаются разными, если они имеют разные строки запроса, заголовки Vary и/или методы (GET, POST, PUT и т. д.).
Вы можете игнорировать некоторые или все эти вещи, передавая объект параметров в качестве второго параметра.
const options = { ignoreSearch: true, ignoreMethod: true, ignoreVary: true }; cache.match(request, options).then(...);
Если более одного кэшированного запроса совпадает, возвращается тот, который был создан первым.
Если вы хотите получить все соответствующие ответы, можно использовать cache.matchAll.
const options = { ignoreSearch: true, ignoreMethod: true, ignoreVary: true }; cache.matchAll(request, options).then((responses) => { 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. Поскольку эта операция была разработана для IndexedDB, она имеет гораздо лучшую производительность при большом количестве записей.
Если вы храните URL-адрес Request вместе со свойствами поиска, можно легко получить правильную запись в кэше после выполнения поиска.
Есть три способа добавить элемент в кеш — put, add и addAll. Все три метода возвращают Promise.
cache.put
Синтаксис cache.put (request, response). request — это либо объект Request, либо строка — если это строка, то вместо нее используется new Request(request). response должен быть объектом Response. Эта пара хранится в кеше.
cache.put('/test.json', new Response('{"foo": "bar"}'));
cache.add
Синтаксис cache.add (request). request обрабатывается так же, как и для put, но Response, который хранится в кэше, является результатом выборки запроса из сети. Если выборка не удалась или если код состояния ответа не находится в диапазоне 200, то ничего не сохраняется и promises отклоняется. Обратите внимание, что запросы между источниками, не находящиеся в режиме CORS, имеют статус 0, и поэтому такие запросы могут храниться только с put.
cache.addAll
Синтаксис cache.addAll (requests), где requests — это массив Request
или строк URL. Это работает аналогично вызову cache.add для каждого отдельного запроса, за исключением того, что Promise отклоняется, если какой-либо отдельный запрос не кэшируется.
В каждом из этих случаев новая запись перезаписывает любую соответствующую существующую запись. При этом используются те же правила сопоставления, которые описаны в разделе о получении.
Удаление элемента из кэша:
cache.delete(request);
Где запрос может быть Request или строкой URL. Этот метод также принимает тот же объект параметров, что и cache.match, который позволяет удалить несколько пар «Request/Response» для одного и того же URL-адреса.
cache.delete('/example/file.txt', { ignoreVary: true, ignoreSearch: true });
Чтобы удалить кеш, вызовите caches.delete(name). Эта функция возвращает Promise, который принимает значение true, если кэш существовал и был удален, или false в противном случае.
Простой пример, демонстрирующий использование service worker для предварительного кэширования приложения JavaScript.
Этот пример показывает как можно улучшить взаимодействие пользователя с веб-страницой, которая динамически загружает какое нибудь тяжелое приложение в iFrame.
Чтобы проиллюстрировать проблему, мы используем простой пример, состоящий из страницы (index.html) и с кнопкой на ней.
Нажатие на кнопку создает iFrame, который загружает вторую страницу (iframe.html), которая, в свою очередь, загружает приложение JavaScript (script.js); представьте, что этот файл большой (скажем, какое нибудь тяжелое приложение).
Хорошей новостью является то, что мы откладываем загрузку большого файла JavaScript, пока не нажмем кнопку. Плохая новость заключается в том, что после нажатия кнопки браузер должен (медленно) загрузить файл перед запуском приложения JavaScript в iFrame.
Код проблемы особенно прост.
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Parent</title> </head> <body> <button>Open iframe</button> <script src="app.js"></script> </body> </html>
app.js
var openButtonEl = document.getElementById('open-button'); openButtonEl.addEventListener('click', handleClick); function handleClick() { openButtonEl.setAttribute('disabled', true); var iframeEl = document.createElement('iframe'); iframeEl.src = 'iframe.html'; document.body.appendChild(iframeEl); }
iframe.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Iframe window</title> </head> <body> <div>Iframe window</div> <script src='script.js'></script> </body> </html>
script.js
console.log('Hello from Iframe');
Общая идея состоит в том, что мы хотим, чтобы «большой» файл script.js кэшировался асинхронно при загрузке первой страницы index.html. При этом, когда мы нажимаем кнопку, script.js брался бы из кэша (быстро).
Ключом к этому решению является использование service worker
Сначала мы добавляем код загрузчика service worker в…
app.js
var openButtonEl = document.getElementById('open-button'); openButtonEl.addEventListener('click', handleClick); if ('serviceWorker' in navigator) { navigator.serviceWorker.register('worker.js').then( handleRegisterSuccess, handleRegisterError, ); } function handleClick() { openButtonEl.setAttribute('disabled', true); var iframeEl = document.createElement('iframe'); iframeEl.src = 'iframe.html'; document.body.appendChild(iframeEl); } function handleRegisterSuccess(registration) { console.log('ServiceWorker registration successful with scope: ', registration.scope); } function handleRegisterError(err) { console.log('ServiceWorker registration failed: ', err); }
и создадим код service worker …
worker.js
console.log('LOAD'); var CACHE_NAME = 'cache'; var URLS = [ 'script.js', ]; self.addEventListener('install', handleInstall); self.addEventListener('activate', handleActivate); self.addEventListener('fetch', handleFetch); function handleInstall(event) { console.log('INSTALL'); event.waitUntil( caches.open(CACHE_NAME).then(handleCacheOpen) ); function handleCacheOpen(cache) { console.log('CACHE OPEN'); return cache.addAll(URLS); } } function handleActivate() { console.log('ACTIVATE'); } function handleFetch(event) { console.log('FETCH'); console.log(event.request.url); if (event.request.url === 'http://localhost:8080/') { console.log('IGNORE HOST PAGE'); return; } event.respondWith( caches.match(event.request).then(handleMatch) ); function handleMatch(response) { console.log('MATCH'); console.log(response); return response || fetch(event.request); } }
Во-первых, чтобы избежать путаницы между кэшем браузера и кешем service worker, я отключил кэш браузера, настроив сервер на немедленное истечение срока действия контента; используя http-server.
npx http-server -c-1
Используя вкладку «Network» в Chrome Developer Tools, мы видим, что при первоначальной загрузке index.html, worker.js загружается асинхронно…
… И запускает service worker (как видно на вкладке «Application»)
service worker, в свою очередь, загружает script.js в кеш; см. вкладку «Network» выше и вкладку «Application» ниже.
Когда мы затем нажимаем кнопку, index.html получает iframe.html от service worker (который, в свою очередь, получает его от веб-сервера); как видно на вкладке Network.
С другой стороны, index.html получает script.js от service worker (который, в свою очередь, получает его из кэша service worker );
Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…
Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…
Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…
Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…
Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…
Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…