渐进式 Web 应用的一个关键方面是它们可靠;即使在网络状况不佳时,它们也可以快速加载资源,持续吸引用户并立即提供反馈。为什么会这样?感谢 Service Worker fetch
事件。
fetch 事件
借助 fetch
事件,我们可以针对同源和跨源请求,拦截 PWA 在 Service Worker 作用域内发出的每个网络请求。除了导航和资产请求之外,通过已安装的 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 对象。但是,您不能在提取事件处理脚本完成后调用 respondWith()
,比如在异步调用中。如果您需要等待完整响应,可以将 Promise 传递给使用 Response 进行解析的 respondWith()
。
创建响应
得益于 Fetch API,您可以在 JavaScript 代码中创建 HTTP 响应,并且这些响应可以使用 Cache Storage API 进行缓存,并像来自网络服务器一样返回。
如需创建响应,请创建一个新的 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 响应,接下来可以使用缓存存储接口在设备上存储资源。
您可以使用 Cache Storage API 检查从 PWA 收到的请求在缓存中是否可用,如果可用,则用它响应 respondWith()
。
为此,您首先需要在缓存内进行搜索。match()
函数(位于顶级 caches
界面中)可搜索源站中的所有存储区,或搜索单个打开的缓存对象。
match()
函数接收 HTTP 请求或网址作为参数,并返回使用与相应键关联的响应进行解析的 promise。
// 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 分发资源。您不限于一种缓存策略。您可以为不同的网址格式定义不同的网址。例如,您可以为最少界面资源使用一种策略,针对 API 调用使用另一种策略,第三种策略用于图片和数据网址。为此,请阅读 ServiceWorkerGlobalScope.onfetch
中的 event.request.url
,并通过正则表达式或网址格式对其进行解析。(在撰写本文时,并非所有平台都支持网址格式)。
最常用的策略包括:
- 缓存优先
- 首先搜索缓存的响应,如果未找到,则回退到网络。
- 广告联盟优先
- 首先从网络请求响应,如果未返回任何响应,则在缓存中检查响应。
- 重新验证时过时
- 从缓存提供响应,同时在后台请求最新版本并将其保存到缓存中,以便下次请求资源时使用。
- 仅限网络
- 一律使用网络回复或出错。但系统永远不会查询缓存。
- 仅缓存
- 始终使用缓存返回的响应或输出错误进行回复。系统绝不会查询广告网络。使用此策略提供的资源必须先添加到缓存中,然后再请求这些资源。
缓存优先
使用此策略,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);
}
)
)
});
网络优先
此策略是“缓存优先”策略的镜像;它会检查是否可以从网络满足请求,如果不能满足,就会尝试从缓存中检索。像优先缓存一样。如果既没有网络响应也没有缓存响应,则请求将会出错。从网络获取响应通常比从缓存中获取响应速度慢,此策略会优先考虑更新的内容而不是性能。
self.addEventListener("fetch", event => {
event.respondWith(
fetch(event.request)
.catch(error => {
return caches.match(event.request) ;
})
);
});
重新验证时过时
过时的“重新验证”策略会立即返回缓存的响应,然后检查网络是否有更新,如果发现缓存响应,则会替换该响应。此策略始终会发出网络请求,因为即使找到了缓存的资源,它也会尝试使用从网络接收的内容更新缓存中的内容,以便在下一个请求中使用更新后的版本。因此,此策略可让您从缓存优先策略的快速提供中受益,并在后台更新缓存。
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
}
)
)
})
仅限网络
“仅限网络”策略类似于浏览器在不使用 Service Worker 或 Cache Storage API 的情况下的行为。只有在可以从网络中提取资源时,请求才会返回资源。这通常对于仅在线 API 请求等资源非常有用。
仅缓存
“仅缓存”策略可确保请求永远不会进入网络;所有传入请求均以预填充的缓存项作为响应。以下代码结合使用 fetch
事件处理脚本和缓存存储空间的 match
方法,以便仅响应缓存:
self.addEventListener("fetch", event => {
event.respondWith(caches.match(event.request));
});
自定义策略
虽然以上是常用的缓存策略,但您负责管理 Service Worker 以及请求的处理方式。如果上述方法均无法满足您的需求,请自行创建。
例如,您可以使用带有超时的“广告联盟优先”策略来优先考虑更新的内容,但前提是相应响应未超出您设置的阈值。您还可以将缓存的响应与网络响应合并,并通过 Service Worker 构建复杂的响应。
正在更新资产
使 PWA 的缓存资源保持最新状态是一项挑战。虽然在重新验证策略时过时只是其中一种方法,但并非唯一的方法。在更新章节中,您将了解更新应用内容和资源的不同技巧。