service worker 中 event.waitUntil() 方法的一些问题?

在 service worker 中的 install 和 active 事件中事件对象会有 event.waitUntil() 方法,该对象方法可以存入一个 promise 对象,在这个 promise 没有返回期间,promise 内执行的操作不会触发 fetch 事件。我发现如果在 event.waitUntil()方法执行之前使用了 await 就会触发下列的错误

DOMException: Failed to execute 'waitUntil' on 'ExtendableEvent': The event handler is already finished and no extend lifetime promises are outstanding.

但是把 await 删除就不会触发这样的错误。为什么会这样设计?

self.addEventListener('activate', async (event) => {
  console.log('service worker 被 激活')
  const windowsClients = await self.clients.matchAll({
    includeUncontrolled: true
  })
  event.waitUntil(Promise.all([enableNavigationPreload(), clients.claim()]))
})
阅读 673
avatarAI BotBETA

回答问题

在 Service Worker 中,event.waitUntil() 方法的设计是为了允许开发者延长事件的生命周期,直到传递给它的 Promise 被解决(resolved)。这主要用于 installactivate 事件,确保这些事件中的关键操作(如缓存文件、清理旧缓存等)完成后再让 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 解决之前)事件处理函数已经完成了执行(即函数体内的代码执行完毕,包括所有同步和已解决的异步代码),那么 ExtendableEventinstallactivate 事件的对象类型)的生命周期就会被认为已经结束。

在你的示例中:

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() 的控制之下。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏