渐进式 Web 应用的一个重要方面是可靠;它们可以快速加载资源,从而保持用户的活跃度并立即提供反馈,即使在网络条件较差的情况下也可以。为什么会这样?这要归功于 Service Worker fetch
事件。
提取事件
浏览器支持
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
通过 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()
。
为此,您首先需要在缓存中进行搜索。顶级 caches
接口中的 match()
函数可搜索您来源中的所有商店,或针对单个打开的缓存对象。
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 会在缓存中查找匹配的请求,并在缓存缓存后返回相应的响应。否则,它会从网络检索响应(可以选择更新缓存以供将来的调用使用)。如果既没有缓存响应,也没有网络响应,则请求将出错。由于不向网络投放素材资源的速度往往会更快,因此该策略优先考虑性能而非时效性。
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 的缓存资源保持最新可能是一项挑战。虽然“过时的重新验证”策略是实现这一目的的一种方式,但并不是唯一的。在“更新”这一章中,您将了解更新应用内容和资产的不同技巧。