渐进式 Web 应用的一个关键方面是它们的可靠性;即使在网络条件不佳的情况下,它们也能快速加载资源,让用户保持参与并立即提供反馈。这是如何实现的?这要归功于 Service Worker 的 fetch
事件。
Fetch 事件
fetch
事件使我们能够拦截 Service Worker 作用域内 PWA 发出的每个网络请求,包括同源和跨源请求。除了导航和资源请求之外,从已安装的 Service Worker 进行提取还允许在站点首次加载后无需网络调用即可呈现页面访问。
fetch
处理程序接收来自应用的所有请求,包括网址和 HTTP 标头,并允许应用开发者决定如何处理它们。
您的 Service Worker 可以将请求转发到网络,使用先前缓存的响应进行响应,或创建新的响应。选择权在您。这是一个简单的示例
self.addEventListener("fetch", event => {
console.log(`URL requested: ${event.request.url}`);
});
响应请求
当请求进入您的 Service Worker 时,您可以执行两件事:您可以忽略它,让它转到网络;或者您可以响应它。从 Service Worker 内部响应请求是您能够选择将什么以及如何返回到 PWA 的方式,即使在用户离线时也是如此。
要响应传入的请求,请从 fetch
事件处理程序中调用 event.respondWith()
,如下所示
// fetch event handler in your service worker file
self.addEventListener("fetch", event => {
const response = .... // a response or a Promise of response
event.respondWith(response);
});
您必须同步调用 respondWith()
,并且必须返回 Response 对象。但是,您不能在 fetch 事件处理程序完成后调用 respondWith()
,例如在异步调用中。如果您需要等待完整响应,可以将 Promise 传递给 respondWith()
,该 Promise 使用 Response 进行解析。
创建响应
借助 Fetch API,您可以在 JavaScript 代码中创建 HTTP 响应,并且可以使用 Cache Storage API 缓存这些响应并返回,就像它们来自 Web 服务器一样。
要创建响应,请创建一个新的 Response
对象,设置其正文和选项,例如状态和标头
const simpleResponse = new Response("Body of the HTTP response");
const options = {
status: 200,
headers: {
'Content-type': 'text/html'
}
};
const htmlResponse = new Response("<b>HTML</b> content", options)
从缓存响应
现在您已经了解了如何从 Service Worker 提供 HTTP 响应,现在是时候使用 Caching Storage 接口在设备上存储资源了。
您可以使用 Cache Storage API 检查从 PWA 收到的请求是否在缓存中可用,如果是,则使用它响应 respondWith()
。为此,您首先需要在缓存中搜索。match()
函数(在顶层 caches
接口中可用)在您的来源中的所有存储或单个打开的缓存对象上搜索。
match()
函数接收 HTTP 请求或网址作为参数,并返回一个 Promise,该 Promise 使用与相应键关联的 Response 进行解析。
// Global search on all caches in the current origin
caches.match(urlOrRequest).then(response => {
console.log(response ? response : "It's not in the cache");
});
// Cache-specific search
caches.open("pwa-assets").then(cache => {
cache.match(urlOrRequest).then(response => {
console.log(response ? response : "It's not in the cache");
});
});
缓存策略
仅从浏览器缓存提供文件并不适合所有用例。例如,用户或浏览器可以逐出缓存。这就是为什么您应该为 PWA 定义自己的资源传递策略。您不限于一种缓存策略。您可以为不同的网址模式定义不同的策略。例如,您可以为最小 UI 资源使用一种策略,为 API 调用使用另一种策略,为图像和数据网址使用第三种策略。为此,请在 ServiceWorkerGlobalScope.onfetch
中读取 event.request.url
并通过正则表达式或 URL 模式对其进行解析。(在撰写本文时,并非所有平台都支持 URL 模式)。
最常见的策略是
- Cache First
- 首先搜索缓存的响应,如果未找到,则回退到网络。
- Network First
- 首先从网络请求响应,如果没有返回响应,则检查缓存中的响应。
- Stale While Revalidate
- 从缓存提供响应,同时在后台请求最新版本并将其保存到缓存中,以便下次请求资源时使用。
- Network-Only
- 始终使用来自网络的响应进行回复,或者出错。永远不会查询缓存。
- Cache-Only
- 始终使用来自缓存的响应进行回复,或者出错。永远不会查询网络。使用此策略提供的资源必须在请求之前添加到缓存中。
Cache First
使用此策略,Service Worker 在缓存中查找匹配的请求,如果已缓存,则返回相应的 Response。否则,它将从网络检索响应(可选,更新缓存以供将来调用)。如果既没有缓存响应也没有网络响应,则请求将出错。由于不访问网络提供资源往往更快,因此该策略优先考虑性能而不是新鲜度。
self.addEventListener("fetch", event => {
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
// It can update the cache to serve updated content on the next request
return cachedResponse || fetch(event.request);
}
)
)
});
Network First
此策略是 Cache First 策略的镜像;它检查是否可以从网络满足请求,如果不能,则尝试从缓存中检索它。与 Cache First 类似。如果既没有网络响应也没有缓存响应,则请求将出错。从网络获取响应通常比从缓存获取响应慢,此策略优先考虑更新的内容而不是性能。
self.addEventListener("fetch", event => {
event.respondWith(
fetch(event.request)
.catch(error => {
return caches.match(event.request) ;
})
);
});
Stale While Revalidate
Stale While Revalidate 策略立即返回缓存的响应,然后检查网络以进行更新,如果找到更新,则替换缓存的响应。此策略始终发出网络请求,因为即使找到缓存的资源,它也会尝试使用从网络接收的内容更新缓存中的内容,以便在下一个请求中使用更新后的版本。因此,此策略为您提供了一种方法,可以从 Cache First 策略的快速服务中受益,并在后台更新缓存。
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(cachedResponse => {
const networkFetch = fetch(event.request).then(response => {
// update the cache with a clone of the network response
const responseClone = response.clone()
caches.open(url.searchParams.get('name')).then(cache => {
cache.put(event.request, responseClone)
})
return response
}).catch(function (reason) {
console.error('ServiceWorker fetch failed: ', reason)
})
// prioritize cached response over network
return cachedResponse || networkFetch
}
)
)
})
Network Only
Network Only 策略类似于浏览器在没有 Service Worker 或 Cache Storage API 的情况下的行为方式。仅当可以从网络提取资源时,请求才会返回资源。这通常对于仅限在线的 API 请求等资源很有用。
Cache Only
Cache Only 策略确保请求永远不会转到网络;所有传入的请求都使用预填充的缓存项进行响应。以下代码将 fetch
事件处理程序与 Cache Storage 的 match
方法结合使用以响应 Cache Only
self.addEventListener("fetch", event => {
event.respondWith(caches.match(event.request));
});
自定义策略
虽然以上是常见的缓存策略,但您负责您的 Service Worker 以及如何处理请求。如果这些策略都不适合您的需求,请创建您自己的策略。
例如,您可以使用具有超时的 Network First 策略来优先考虑更新的内容,但前提是响应在您设置的阈值内出现。您还可以将缓存的响应与网络响应合并,并从 Service Worker 构建复杂的响应。
更新资源
使 PWA 的缓存资源保持最新可能是一个挑战。虽然 Stale While Revalidate 策略是其中一种方法,但它不是唯一的方法。在更新章节中,您将学习不同的技术来保持应用的内容和资源更新。