回答问题
在 Service Worker 中,event.waitUntil()
方法的设计是为了允许开发者延长事件的生命周期,直到传递给它的 Promise 被解决(resolved)。这主要用于 install
和 activate
事件,确保这些事件中的关键操作(如缓存文件、清理旧缓存等)完成后再让 Service Worker 进入下一个状态。
为什么会出现错误?
错误 DOMException: Failed to execute 'waitUntil' on 'ExtendableEvent': The event handler is already finished and no extend lifetime promises are outstanding.
发生的原因是,当你在事件处理函数中使用了 await
关键字时,它会暂停当前函数的执行,并等待 Promise 解决。然而,如果在 await
调用之后(即 Promise 解决之前)事件处理函数已经完成了执行(即函数体内的代码执行完毕,包括所有同步和已解决的异步代码),那么 ExtendableEvent
(install
和 activate
事件的对象类型)的生命周期就会被认为已经结束。
在你的示例中:
self.addEventListener('activate', async (event) => {
console.log('service worker 被 激活')
const windowsClients = await self.clients.matchAll({
includeUncontrolled: true
})
// 当到达这里时,如果 windowsClients 的 Promise 已经解决,但事件处理函数还未调用 event.waitUntil(),
// 则事件的生命周期可能已经结束,导致调用 event.waitUntil() 时抛出错误。
event.waitUntil(Promise.all([enableNavigationPreload(), clients.claim()]))
})
解决方案
确保在事件处理函数结束之前调用 event.waitUntil()
,并且所有可能影响其生命周期的异步操作都应该在调用 event.waitUntil()
之前完成或包含在其 Promise 数组中。你可以通过调整代码顺序来实现这一点:
self.addEventListener('activate', async (event) => {
console.log('service worker 被 激活');
// 将所有需要等待的操作放入同一个 Promise.all 中
event.waitUntil(Promise.all([
self.clients.matchAll({ includeUncontrolled: true }), // 虽然这里的结果可能未直接使用,但可以避免错误
enableNavigationPreload(),
clients.claim()
]).then(() => {
// 这里可以处理匹配到的 windowsClients,但不影响 event.waitUntil 的行为
}));
});
注意,虽然在这个特定的例子中,self.clients.matchAll()
的结果可能并未在后续代码中被直接使用,但将其包含在 Promise.all
中可以避免因为等待其完成而导致的事件生命周期提前结束的问题。同时,这也确保了所有必要的异步操作都在 event.waitUntil()
的控制之下。