Service Worker中消息监听器与Fetch监听器的竞态条件问题求助
Service Worker中消息监听器与Fetch监听器的竞态条件问题求助
看起来你遇到的是Service Worker启动时异步消息和fetch事件的典型竞态问题——用timeout确实是治标不治本的办法,因为Service Worker的事件循环顺序没法靠固定时长的延迟来保证,而且fetch事件的respondWith必须同步调用,事后再改肯定会报错。咱们可以用Promise来做异步流程的同步化,让fetch请求乖乖等header更新完成再执行。
核心思路
我们需要在Service Worker里维护一个Promise,用来标记secureHeader是否已经准备就绪。当SW启动发现header为空时,先创建一个pending状态的Promise,等页面通过message把正确的header发回来后,再resolve这个Promise。而fetch事件则会等待这个Promise完成后,再携带正确的header发起请求。
修改后的代码示例
console.log('top root log'); // 初始化header和对应的就绪Promise if (!self.secureHeader) { self.secureHeader = 'Empty'; // 创建一个pending的Promise,用于等待header更新 self.headerReady = new Promise((resolve) => { // 把resolve方法存起来,等收到消息时调用 self.resolveHeaderReady = resolve; }); console.log('Refreshing header'); // 触发页面刷新header(用clients.matchAll确保能发消息到页面) self.clients.matchAll({ includeUncontrolled: true, type: 'window' }).then(clients => { clients.forEach(client => { client.postMessage({ type: 'refreshHeader', msg: '' }); }); }); } self.addEventListener('message', function (event) { if (event.data.type === 'setHeader') { self.secureHeader = event.data.msg; console.log('Header received:'+ self.secureHeader); // 如果有等待的Promise,就resolve它 if (self.resolveHeaderReady) { self.resolveHeaderReady(); // 用完可以清掉,避免重复调用 delete self.resolveHeaderReady; } // 后续如果还有header更新需求,直接设为已完成的Promise self.headerReady = Promise.resolve(); } }, false); self.addEventListener('fetch', function(e) { // 先等待header就绪,再处理请求 e.respondWith( (async function() { // 如果header还没准备好,等待Promise完成 if (self.secureHeader === 'Empty') { await self.headerReady; } console.log(`Responding: ${self.secureHeader}`); // 构建带安全头的请求 const request = e.request; const modifiedRequest = new Request(request, { headers: new Headers(request.headers) }); modifiedRequest.headers.set('你的自定义安全头名称', self.secureHeader); try { return await fetch(modifiedRequest); } catch (err) { // 网络失败时回退到缓存 return await caches.match(request); } })() ); });
关键改动说明
就绪Promise的设计:
- 当SW首次初始化时,创建一个pending的
headerReadyPromise,并把它的resolve方法存到全局,这样收到message时可以手动触发完成。 - 这样不管fetch事件触发得有多早,都会等待这个Promise完成后再执行后续逻辑,从根源上避免竞态。
- 当SW首次初始化时,创建一个pending的
消息监听器的扩展:
- 收到
setHeader消息时,除了更新secureHeader,还要调用之前存的resolveHeaderReady,让等待中的Promise完成。 - 后续如果还有header更新需求,直接把
headerReady设为已resolve的Promise,避免重复等待。
- 收到
fetch事件的异步处理:
- 用
async/await包裹逻辑,在respondWith里直接返回这个异步函数的结果(respondWith本身支持接收Promise)。 - 先判断如果header还是Empty,就
await self.headerReady,确保拿到正确的header后再发起请求。
- 用
流程验证
当SW因长时间休眠被唤醒后:
- 初始化
secureHeader为Empty,创建pending的headerReadyPromise,发送refreshHeader消息给页面。 - 页面收到消息后,计算并发送
setHeader消息给SW。 - SW的message监听器设置
secureHeader并resolveheaderReady。 - 此时如果fetch事件已经触发,会在
await self.headerReady处等待,直到Promise完成后再携带正确的header发起请求。
这样就彻底解决了竞态问题,比timeout可靠多了——它是基于异步事件的完成状态来同步流程,而不是靠固定的延迟时长。
备注:内容来源于stack exchange,提问作者Shaw
相关产品推荐
相关产品推荐

