You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
            }
        })()
    );
});

关键改动说明

  1. 就绪Promise的设计:

    • 当SW首次初始化时,创建一个pending的headerReady Promise,并把它的resolve方法存到全局,这样收到message时可以手动触发完成。
    • 这样不管fetch事件触发得有多早,都会等待这个Promise完成后再执行后续逻辑,从根源上避免竞态。
  2. 消息监听器的扩展:

    • 收到setHeader消息时,除了更新secureHeader,还要调用之前存的resolveHeaderReady,让等待中的Promise完成。
    • 后续如果还有header更新需求,直接把headerReady设为已resolve的Promise,避免重复等待。
  3. fetch事件的异步处理:

    • 用async/await包裹逻辑,在respondWith里直接返回这个异步函数的结果(respondWith本身支持接收Promise)。
    • 先判断如果header还是Empty,就await self.headerReady,确保拿到正确的header后再发起请求。

流程验证

当SW因长时间休眠被唤醒后:

  1. 初始化secureHeader为Empty,创建pending的headerReady Promise,发送refreshHeader消息给页面。
  2. 页面收到消息后,计算并发送setHeader消息给SW。
  3. SW的message监听器设置secureHeader并resolveheaderReady。
  4. 此时如果fetch事件已经触发,会在await self.headerReady处等待,直到Promise完成后再携带正确的header发起请求。

这样就彻底解决了竞态问题,比timeout可靠多了——它是基于异步事件的完成状态来同步流程,而不是靠固定的延迟时长。

备注:内容来源于stack exchange,提问作者Shaw

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 16:34:33