渐进式 Web 应用的一个关键方面是可靠性;它们可以快速加载资源,让用户保持互动并立即提供反馈,即使在网络状况不佳的情况下也是如此。为什么会这样?感谢服务工件 fetch
事件。
提取事件
借助 fetch
事件,我们可以在服务工件的作用域内拦截 PWA 发出的所有网络请求,包括同源请求和跨源请求。除了导航和资源请求之外,从已安装的服务工件提取内容还允许在网站首次加载后呈现网页访问内容,而无需进行网络调用。
fetch
处理程序会接收来自应用的所有请求(包括网址和 HTTP 标头),并让应用开发者决定如何处理这些请求。
您的服务工作器可以将请求转发到网络、使用之前缓存的响应进行响应,或创建新响应。一切由您选择。 下面是一个简单示例:
self.addEventListener("fetch", event => {
console.log(`URL requested: ${event.request.url}`);
});
响应请求
当请求进入您的服务工件时,您可以执行以下两项操作:忽略请求(让请求进入网络),或响应请求。通过从服务工件中响应请求,您可以选择要将哪些内容以何种方式返回到 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 传递给 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)
从缓存中响应
现在,您已经了解如何从服务工件提供 HTTP 响应,接下来可以使用缓存存储空间接口在设备上存储资源了。
您可以使用缓存存储 API 检查从 PWA 收到的请求是否在缓存中,如果在,则使用该请求响应 respondWith()
。为此,您首先需要在缓存中搜索。match()
函数可在顶级 caches
接口中使用,用于搜索源中的所有存储区,或单个打开的缓存对象。
match()
函数会接收 HTTP 请求或网址作为参数,并返回一个 promise,该 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
,并通过正则表达式或网址格式对其进行解析。(在撰写本文时,并非所有平台都支持网址格式)。
最常见的策略包括:
- 先缓存
- 首先搜索缓存的响应,如果未找到,则回退到网络。
- 先显示影音平台
- 首先从网络请求响应,如果未返回任何响应,则在缓存中检查响应。
- 在重新验证时过时
- 从缓存中提供响应,同时在后台请求最新版本并将其保存到缓存,以备下次请求资源时使用。
- 仅限影音平台
- 始终使用来自网络的响应进行回复,或者出错。系统永远不会咨询缓存。
- 仅缓存
- 始终使用缓存中的响应进行回复,或者会出错。系统绝不会咨询该广告联盟。使用此策略提供的资源必须先添加到缓存中,然后才能被请求。
先缓存
采用此策略时,服务工件会在缓存中查找匹配的请求,并在缓存中找到相应响应时返回该响应。否则,它会从网络检索响应(可选地,更新缓存以供日后调用)。如果既没有缓存响应,也没有网络响应,请求将出错。由于不通过网络分发资源的速度往往更快,因此此策略会优先考虑性能而非新鲜度。
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));
});
自定义策略
虽然上述是常见的缓存策略,但您负责管理您的服务工作器以及请求的处理方式。如果这些都不符合您的需求,您可以自行创建。
例如,您可以使用带有超时的网络优先策略来优先显示更新的内容,但前提是响应出现在您设置的阈值内。您还可以将缓存的响应与网络响应合并,并通过服务工件构建复杂的响应。
更新素材资源
让 PWA 的缓存资源保持最新状态可能并非易事。虽然“在重新验证时使用过时数据”策略是一种方法,但并不是唯一的方法。在“更新”章节中,您将学习各种方法来及时更新应用的内容和资源。