缓存存储是一个强大的工具。它可以降低您的应用对网络条件的依赖性。通过合理使用缓存,您可以使您的 Web 应用在离线状态下可用,并在任何网络条件下尽可能快地提供您的资源。正如在资源和数据中提到的,您可以决定缓存必要资源的最佳策略。为了管理缓存,您的 Service Worker 与 Cache Storage API 交互。
Cache Storage API 可从不同的上下文中获得
- 窗口上下文(PWA 的主线程)。
- Service Worker。
- 您使用的任何其他 Worker。
使用 Service Worker 管理缓存的一个优势是其生命周期不受窗口的限制,这意味着您不会阻塞主线程。请注意,要使用 Cache Storage API,大多数这些上下文都必须在 TLS 连接下。
要缓存的内容
关于缓存,您可能首先要问的问题是要缓存什么。虽然这个问题没有唯一的答案,但您可以从渲染用户界面所需的所有最少资源开始。
这些资源应包括
- 主页 HTML(应用的 start_url)。
- 主用户界面所需的 CSS 样式表。
- 用户界面中使用的图片。
- 渲染用户界面所需的 JavaScript 文件。
- 数据,例如渲染基本体验所需的 JSON 文件。
- Web 字体。
- 在多页面应用上,您想要快速提供或在离线状态下提供的其他 HTML 文档。
可离线使用
虽然具备离线功能是渐进式 Web 应用的要求之一,但务必了解并非每个 PWA 都需要完整的离线体验,例如云游戏解决方案或加密资产应用。因此,在这些情况下提供引导用户的基本用户界面是可以接受的。
您的 PWA 不应渲染浏览器的错误消息,指出 Web 渲染引擎无法加载页面。而是使用您的 Service Worker 显示您自己的消息,避免通用的且令人困惑的浏览器错误。
根据 PWA 的需求,您可以使用许多不同的缓存策略。这就是为什么设计缓存使用以提供快速可靠的体验非常重要的原因。例如,如果您的所有应用资源都将快速下载,不占用大量空间,并且不需要在每次请求中都更新,则缓存所有资源将是一个有效的策略。另一方面,如果您有需要最新版本的资源,您可能需要考虑完全不缓存这些资源。
使用 API
使用 Cache Storage API 在您的来源内定义一组缓存,每个缓存都用您可以定义的字符串名称标识。通过 caches
对象访问 API,open
方法可以创建或打开已创建的缓存。“open”方法返回缓存对象的 Promise。
caches.open("pwa-assets")
.then(cache => {
// you can download and store, delete or update resources with cache arguments
});
下载和存储资源
要请求浏览器下载和存储资源,请使用 add
或 addAll
方法。add
方法发出请求并存储一个 HTTP 响应,而 addAll
方法基于请求或 URL 数组以事务方式存储一组 HTTP 响应。
caches.open("pwa-assets")
.then(cache => {
cache.add("styles.css"); // it stores only one resource
cache.addAll(["styles.css", "app.js"]); // it stores two resources
});
缓存存储接口存储整个响应,包括所有标头和正文。因此,您可以稍后使用 HTTP 请求或 URL 作为键来检索它。您将在提供章节中了解如何执行此操作。
何时缓存
在您的 PWA 中,您负责决定何时缓存文件。虽然一种方法是在安装 Service Worker 时存储尽可能多的资源,但这通常不是最佳方案。缓存不必要的资源会浪费带宽和存储空间,并可能导致您的应用提供意外的过时资源。
您无需一次缓存所有资源,您可以在 PWA 的生命周期中多次缓存资源,例如
- 在安装 Service Worker 时。
- 在首次页面加载后。
- 当用户导航到某个部分或路由时。
- 当网络空闲时。
您可以在主线程或 Service Worker 上下文中请求缓存新文件。
在 Service Worker 中缓存资源
最常见的场景之一是在安装 Service Worker 时缓存一组最少的资源。为此,您可以在 Service Worker 的 install
事件中使用缓存存储接口。
由于 Service Worker 线程可能随时停止,您可以请求浏览器等待 addAll
Promise 完成,以增加存储所有资源并保持应用一致性的机会。以下示例演示了如何使用 Service Worker 事件侦听器中接收到的事件参数的 waitUntil
方法来执行此操作。
const urlsToCache = ["/", "app.js", "styles.css", "logo.svg"];
self.addEventListener("install", event => {
event.waitUntil(
caches.open("pwa-assets")
.then(cache => {
return cache.addAll(urlsToCache);
});
);
});
waitUntil()
方法接收一个 Promise,并请求浏览器等待 Promise 中的任务解析(已完成或失败)后再终止 Service Worker 进程。您可能需要链接 Promise 并返回 add()
或 addAll()
调用,以便单个结果到达 waitUntil()
方法。
您还可以使用 async/await 语法处理 Promise。在这种情况下,您需要创建一个异步函数,该函数可以调用 await
,并在调用后将 Promise 返回给 waitUntil()
,如下例所示
const urlsToCache = ["/", "app.js", "styles.css", "logo.svg"];
self.addEventListener("install", (event) => {
let cacheUrls = async () => {
const cache = await caches.open("pwa-assets");
return cache.addAll(urlsToCache);
};
event.waitUntil(cacheUrls());
});
跨域请求和不透明响应
您的 PWA 可以从您的来源和跨域(例如来自第三方 CDN 的内容)下载和缓存资源。对于跨域应用,缓存交互与同源请求非常相似。请求被执行,响应的副本存储在您的缓存中。与其他缓存资源一样,它仅可在您应用的来源中使用。
该资源将作为不透明响应存储,这意味着您的代码将无法查看或修改该响应的内容或标头。此外,不透明响应不会在存储 API 中公开其真实大小,从而影响配额。某些浏览器会公开较大的大小,例如 7Mb,无论文件是否仅为 1Kb。
更新和删除资源
您可以使用 cache.put(request, response)
更新资源,并使用 delete(request)
删除资源。
有关更多详细信息,请查看Cache 对象文档。
调试 Cache Storage
许多浏览器都提供一种在 DevTools 应用程序选项卡中调试缓存存储内容的方法。在那里,您可以看到当前来源内每个缓存的内容。我们将在工具和调试章节中详细介绍这些工具。