当服务工作者进行更新时,它不会以正确的方式控制页面。它进入“等待”状态,等待被激活。
令人惊讶的是,更新的服务程序甚至在刷新页面后都无法控制选项卡。Google解释:
https://developers.google.com/web/fundamentals/instant-and-offline/service-worker/lifecycle
即使您只打开一个选项卡演示,刷新页面也不足以让新版本接管。这是由于浏览器导航的工作原理。导航时,直到收到响应标头后,当前页面才会消失,即使响应包含
Content-Disposition
标头,当前页面也可能会停留。由于此重叠,当前的服务工作者始终在刷新期间控制客户端。要获取更新,请使用当前服务工作程序关闭或导航至所有选项卡。然后,当您再次导航至演示时,您应该会看到这匹马[更新的内容]。
此模式类似于Chrome更新的方式。Chrome的更新会在后台下载,但要等到Chrome重新启动后才能应用。同时,您可以继续使用当前版本而不会中断。但是,这在开发过程中是很痛苦的,但是DevTools有使它变得更容易的方法,我将在本文的后面介绍。
在多个选项卡的情况下,此行为是有意义的。我的应用程序是一个捆绑包,需要原子更新。我们无法混合和匹配旧捆绑包的一部分和新捆绑包的一部分。(在本机应用程序中,原子性是自动的并且有保证。)
但是在单个标签的情况下,我将其称为应用程序的“上次打开的标签”,这不是我想要的。我想刷新上一个打开的选项卡以更新我的服务人员。
(很难想象有人真的希望旧的服务人员在刷新最后一个打开的选项卡后继续运行。Google的“导航重叠”参数在我看来像是一个不幸的错误的很好的借口。)
我的应用程序通常仅在单个标签中使用。在生产中,我希望我的用户仅通过刷新页面就能使用最新代码,但是那将行不通:旧的服务工作者将在整个刷新期间保持控制权。
我不想告诉用户“要接收更新,请确保关闭或导航离开我的应用程序”。我想告诉他们,“只是刷新”。
用户刷新页面后,如何激活更新的服务人员?
编辑:我知道有一个答案是快速,简单和错误的:skipWaiting
在service worker的install事件中。skipWaiting
在旧页面标签打开的情况下,更新下载后,新服务工作者将立即生效。这使得更新不安全地成为非原子的;就像在应用程序运行时替换本机应用程序捆绑包一样。对我来说那不好。我需要等到用户刷新最后打开的选项卡的页面。
我写了一篇博客文章,解释了如何处理。https://redfin.engineering/how-to-fix-the-refresh-button-when-using-service-workers-a8e27af6df68
我在github上也有一个工作示例。https://github.com/dfabulich/service-worker-refresh-sample
有四种方法:
skipWaiting()
在安装上。这很危险,因为您的标签可以同时包含新旧内容。skipWaiting()
安装时,并刷新“controllerchange
更好” 上的所有打开的选项卡,但是当选项卡随机刷新时,您的用户可能会感到惊讶。controllerchange
;安装时,请skipWaiting()
通过应用内“刷新”按钮提示用户。甚至更好,但是用户必须使用应用程序内的“刷新”按钮;浏览器的刷新按钮仍然无法使用。这在Google关于服务工作者的Udacity课程和《Workbox高级指南》以及master
我在Github上的示例分支中都有详细记录。navigate
模式进行获取期间,计算打开的选项卡(“客户端”)。如果只有一个打开的选项卡,则skipWaiting()
立即并通过返回带有Refresh: 0
标题的空白响应来刷新唯一的选项卡。您可以在Github上的示例refresh-last-tab
分支中看到一个有效的示例。全部放在一起...
在服务人员中:
addEventListener('message', messageEvent => {
if (messageEvent.data === 'skipWaiting') return skipWaiting();
});
addEventListener('fetch', event => {
event.respondWith((async () => {
if (event.request.mode === "navigate" &&
event.request.method === "GET" &&
registration.waiting &&
(await clients.matchAll()).length < 2
) {
registration.waiting.postMessage('skipWaiting');
return new Response("", {headers: {"Refresh": "0"}});
}
return await caches.match(event.request) ||
fetch(event.request);
})());
});
在页面中:
function listenForWaitingServiceWorker(reg, callback) {
function awaitStateChange() {
reg.installing.addEventListener('statechange', function() {
if (this.state === 'installed') callback(reg);
});
}
if (!reg) return;
if (reg.waiting) return callback(reg);
if (reg.installing) awaitStateChange();
reg.addEventListener('updatefound', awaitStateChange);
}
// reload once when the new Service Worker starts activating
var refreshing;
navigator.serviceWorker.addEventListener('controllerchange',
function() {
if (refreshing) return;
refreshing = true;
window.location.reload();
}
);
function promptUserToRefresh(reg) {
// this is just an example
// don't use window.confirm in real life; it's terrible
if (window.confirm("New version available! OK to refresh?")) {
reg.waiting.postMessage('skipWaiting');
}
}
listenForWaitingServiceWorker(reg, promptUserToRefresh);
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句