存储 · API
CacheStorage 与 caches 全局对象
发布于
CacheStorage 以 caches 全局对象的形式暴露在窗口、worker 和 Service Worker 中,是该源下具名 Cache 对象的注册表;每个 Cache 是一组 Request/Response 对,Service Worker 可以在离线时用它们应答 fetch 事件。它与 HTTP 缓存相互独立:数据只能通过脚本进出,HTTP 新鲜度头部一概不生效。
caches.open(cacheName)caches.has(cacheName)caches.delete(cacheName)caches.keys()caches.match(request)caches.match(request, options)每个方法都返回 Promise。open() 在具名缓存不存在时创建它,所以在名称里升级版本号(shell-v4)就足以开启一个全新的存储。CacheStorage 上的 match() 按创建顺序搜索全部缓存,返回第一个命中或 undefined。主线程自 Chrome 43、Firefox 41、Safari 11.1 起可用(BCD api.CacheStorage),Service Worker 内自 Chrome 40 起可用。
| 方法 | 参数 | 类型 | 含义 |
|---|---|---|---|
open、has、delete |
cacheName |
string | 缓存名称。区分大小写,作用域为源;同一源上的两个 Service Worker 共享同一命名空间。 |
match |
request |
Request 或 string |
要查找的请求。字符串会按当前基准 URL 转成 Request。 |
match |
options.ignoreSearch |
boolean | 比较 URL 时忽略查询串。默认 false。 |
match |
options.ignoreMethod |
boolean | 允许匹配非 GET 请求。默认 false,因此 POST 不会匹配。 |
match |
options.ignoreVary |
boolean | 忽略已存响应的 Vary 头。默认 false。 |
match |
options.cacheName |
string | 只搜索指定缓存。Chromium 的 CacheStorage.match() 只支持 ignoreSearch 和 cacheName(BCD api.CacheStorage.match)。 |
写入方法在 Cache 上而不在 CacheStorage 上:cache.put(request, response) 存入你已经持有的一对对象,cache.add(request) 与 cache.addAll(requests) 先抓取再存入,cache.delete(request) 删除一条。
| 异常 | 位置 | 触发条件 |
|---|---|---|
TypeError |
cache.put() |
请求 URL 的 scheme 不是 http: 或 https:,响应状态为 206 Partial Content,或响应带 Vary: *。Promise 被拒绝。 |
TypeError |
cache.add()、cache.addAll() |
抓取到的响应不是 ok(状态码不在 200 到 299 之间)。不透明响应状态为 0,因此一律落入此列;要有意存入不透明响应,改用 fetch() 加 put()。 |
QuotaExceededError DOMException |
任何写入 | 该源配额已用尽。Promise 被拒绝,已存条目不受影响。 |
SecurityError DOMException |
任何 caches 方法 |
源是不透明源(没有 allow-same-origin 的沙箱 <iframe>),或该站点的存储被禁用。 |
Cache Storage 不会让条目过期。以 Cache-Control: max-age=60 存入的响应一年后原样返回;新鲜度是 Service Worker 的职责。
CacheStorage 与 Cache 自 Chrome 43、Firefox 41、Safari 11.1 起在所有引擎中可用(BCD api.CacheStorage、api.Cache),窗口、专用 worker 和 Service Worker 一视同仁,且仅限安全源。cache.add() 自 Chrome 46 起要求 HTTPS。唯一缺失该 API 的场景是禁用了站点存储的 WebView 或浏览器,此时每个方法都以 SecurityError 拒绝。
Cache Storage 与 IndexedDB、OPFS 共用该源的单一配额,数值由 navigator.storage.estimate() 报告。不透明响应按填充后的体积计费,避免页面借配额 API 测出跨源资源大小:在 Chromium 中每条不透明响应无论实际多大都计入约 7 MB 用量(Understanding storage quota,developer.chrome.com)。跨源资源用 cors 模式请求,或在加载它的标签上加 crossorigin,响应就不再是不透明的,按真实大小计费。
三个示例分别覆盖安装期预缓存、由 fetch 填充的运行时缓存,以及页面接触 caches 之前需要的特性检测。
带版本的预缓存与 activate 时清理
Section titled “带版本的预缓存与 activate 时清理”缓存名携带版本号。install 填充新缓存;activate 删除所有名称不同的缓存,于是上一次部署的文件被回收而不是堆积。
const CACHE = 'shell-v4';const ASSETS = ['/', '/app.css', '/app.js', '/offline.html'];
self.addEventListener('install', (event) => { event.waitUntil(caches.open(CACHE).then((cache) => cache.addAll(ASSETS)));});
self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((names) => Promise.all(names.filter((name) => name !== CACHE).map((name) => caches.delete(name))) ) );});addAll() 是原子的:四个请求里有一个返回 404,整个调用就以 TypeError 拒绝,install 失败。对应用壳而言这正是你想要的行为。调用 caches.match(event.request) 的 fetch 处理函数会继续从旧缓存应答,直到新 worker 激活。
在一个处理函数里存入并返回响应
Section titled “在一个处理函数里存入并返回响应”Response 的 body 只能读一次。同一响应还要交给页面时,先克隆再 put();不要在 respondWith() 里等待 put() 完成,那会让页面为一次磁盘写入而等待。
self.addEventListener('fetch', (event) => { if (event.request.method !== 'GET') return; event.respondWith( caches.match(event.request).then((hit) => { if (hit) return hit; return fetch(event.request).then((response) => { if (response.ok) { const copy = response.clone(); event.waitUntil(caches.open('runtime-v1').then((cache) => cache.put(event.request, copy))); } return response; }); }) );});response.ok 守卫把 404 和不透明响应挡在缓存之外;没有它,离线用户会把昨天的错误页当作命中拿到。
检测 API 并退回网络
Section titled “检测 API 并退回网络”不安全源上没有 caches,早于「语法」一节所列版本的浏览器也没有。运行在页面里(Service Worker 之外)的代码应先检测再使用。
async function cachedOrNetwork(url) { if (!('caches' in self)) { return fetch(url); // 没有 Cache Storage:纯网络,没有离线副本 } const hit = await caches.match(url); return hit ?? fetch(url);}同一个函数在 worker 和窗口里都能用,因为两者都暴露 self。
- Service Workers: CacheStorage interface(w3.org)
- Understanding storage quota(developer.chrome.com)
- Cache API
- 缓存策略
- StorageManager.estimate()
- 逐出与尽力而为存储
规范
| 规范 | 状态 |
|---|---|
| 无。 | |