为了掌握 Service Worker,我仍在做实验,但我遇到了一个问题,可能是因为我缺乏 JavaScript 和 Service Worker 方面的专业知识。
当我希望新的 Service Worker
skipWaiting()
使用 postMessage()
时,问题就会发生。如果我显示一个带有按钮的弹出窗口,并将调用绑定到那里的 postMessage()
,一切都会正常。如果我直接调用postMessage()
,是不行的。这是一个竞争条件,因为有时它有效,但我无法识别竞争条件。
顺便说一句,
postMessage()
调用有效,服务工作人员正在记录收到消息时应该记录的内容:
// Listen to messages from clients.
self.addEventListener('message', event => {
switch(event.data) {
case 'skipWaiting': self.skipWaiting(); console.log('I skipped waiting... EXTRA');
break;
}
});
这是代码。重要的一点是
if (registration.waiting)
条件。未注释的代码有效,注释的代码无效:
// Register service worker.
if ('serviceWorker' in navigator) {
// Helpers to show and hide the update toast.
let hideUpdateToast = () => {
document.getElementById('update_available').style.visibility = 'hidden';
};
let showUpdateToast = (serviceworker) => {
document.getElementById('update_available').style.visibility = 'visible';
document.getElementById('force_install').onclick = () => {
serviceworker.postMessage('skipWaiting');
hideUpdateToast();
};
document.getElementById('close').onclick = () => hideUpdateToast();
};
window.addEventListener('load', () => {
let refreshing = false;
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (refreshing) return;
refreshing = true;
window.location.reload();
});
navigator.serviceWorker.register('/sw.js').then(registration => {
// A new service worker has been fetched, watch for state changes.
//
// This event is fired EVERY TIME a service worker is fetched and
// succesfully parsed and goes into 'installing' state. This
// happens, too, the very first time the page is visited, the very
// first time a service worker is fetched for this page, when the
// page doesn't have a controller, but in that case there's no new
// version available and the notification must not appear.
//
// So, if the page doesn't have a controller, no notification shown.
registration.addEventListener('updatefound', () => {
// return; // FIXME
registration.installing.onstatechange = function () { // No arrow function because 'this' is needed.
if (this.state == 'installed') {
if (!navigator.serviceWorker.controller) {
console.log('First install for this service worker.');
} else {
console.log('New service worker is ready to activate.');
showUpdateToast(this);
}
}
};
});
// If a service worker is in 'waiting' state, then maybe the user
// dismissed the notification when the service worker was in the
// 'installing' state or maybe the 'updatefound' event was fired
// before it could be listened, or something like that. Anyway, in
// that case the notification has to be shown again.
//
if (registration.waiting) {
console.log('New service worker is waiting.');
// showUpdateToast(registration.waiting);
// The above works, but this DOESN'T WORK.
registration.waiting.postMessage('skipWaiting');
}
}).catch(error => {
console.log('Service worker registration failed!');
console.log(error);
});
});
}
为什么使用按钮
onclick
事件间接调用可以工作,但调用 postMessage()
却不行?
我完全不知所措,我敢打赌答案很简单,但我太盲目了,看不到它。
提前非常感谢。
看起来像是 Chromium 或 WebKit 中的错误,因为此代码在 Firefox 中始终有效,但在 Chrome 和 Edge 中大多数时候失败。
我已向 Chromium 报告了该错误,让我们看看这是一个错误还是我的代码很奇怪。我已经设法构建了尽可能最小的代码,仍然可以重现该问题,它非常小,可以在错误报告中找到。
可以在此处找到报告。
抱歉打扰了,我会继续调查这个问题,但无论我如何尝试,代码仍然时不时地失败,我无法在代码中发现竞争条件。
我遇到了完全相同的问题。事实证明,如果在已安装的 Service Worker 上调用它,而活动的 Service Worker 上仍然有待处理的获取请求,则
skipWaiting
永远不会解析。有趣的是,一旦请求解决,活动的 Service Worker 就会重新加载,所以这显然是 Chrome 的一个错误。这也是为什么它只是有时发生的原因,以及为什么超时或延迟的skipWaiting请求有更高的成功机会,特别是在页面加载时调用它。
因此,您需要等待所有开放网络请求解决后再调用
skipWaiting
。也许其他人更有创意,但我找到了两种方法来防止竞争条件:
如果您的应用程序只有一个客户端,您可以对
XMLHttpRequest
进行猴子修补以跟踪开放网络请求并保留 skipWaiting
的请求,直到所有请求都得到解决。
如果有多个客户端,您将需要引入一个“关闭”例程,这将使软件推迟所有新的提取请求(
return false
而不是您的提取处理程序中的respondWith
),同时等待所有打开的请求被处理。确定后,您可以在安装的软件中安全地调用 skipWaiting
。
有点多,但这是我能够防止在 Chrome 中偶尔遇到这种情况的唯一方法。