service worker 弱网挂起本质是异步逻辑未受控终止,需用 event.waituntil() 包裹完整 fetch 流程、注入 abortsignal 设置超时、失败后返回缓存或兜底响应,并主动清理悬空请求。

Service Worker 在弱网下请求超时后挂起,本质是异步逻辑未受控终止,而非网络本身“卡住”。关键不在延长超时时间,而在明确控制 fetch 生命周期、设置兜底机制、避免 promise 悬空。
fetch 必须包裹在 event.waitUntil() 中
这是最常被忽略的前提。若 fetch 调用未被 event.waitUntil() 包裹,浏览器可能在响应返回前就终止 Service Worker 线程,导致请求看似“挂起”,实则是执行环境已销毁。
- 所有 fetch、cache 操作、indexedDB 读写,只要发生在 fetch 或 sync 事件回调中,都必须纳入 waitUntil 的 Promise 链
- 不要只包裹 fetch,要包裹整个处理流程:打开缓存 → 查询 → 发起请求 → 存储结果 → 清理状态
- 示例写法:event.waitUntil(fetch(req).then(r => r.clone().then(cache.put)).catch(() => caches.match(req)))
为 fetch 显式设置 timeout 和 abort 控制
原生 fetch 不支持 timeout 参数,需手动注入 AbortSignal 并设定超时阈值,防止弱网下长期等待。
- 创建 AbortController,调用 setTimeout(() => controller.abort(), 8000)(如 8 秒)
- 将 signal 传入 fetch:fetch(url, { signal })
- 捕获 AbortError 并返回降级响应(如缓存内容或空对象),而非让 promise 拒绝后无处理
- 注意:Chrome 对导航请求(document 类型)的预加载 timeout 更短,建议对主页面请求单独设更宽松阈值(如 12s)
避免“缓存未命中 → 网络失败 → 无 fallback”死循环
弱网下常见模式:缓存查无结果 → fetch 超时失败 → 未返回任何响应 → 页面白屏或卡 loading。这不是挂起,是逻辑断链。
- fetch 失败后,必须有明确 fallback:优先返回 stale 缓存(caches.match(req, { ignoreSearch: true }))、兜底 HTML 片段,或构造空响应(new Response('{}', { status: 200 }))
- 对非关键资源(如头像、非首屏图片),可直接返回 new Response('', { status: 204 }) 避免阻塞渲染
- 不要依赖“重试自动恢复”——弱网下连续失败概率高,应一次决策、一次响应
清理无效 pending 请求,防止积压
用户快速刷新或切页时,旧 fetch 可能仍在后台运行。若不主动 abort,多个悬空请求会争抢连接数,加剧超时。
- 在 fetch 事件开头检查 event.request.destination !== 'document',对非导航请求启用 requestId + AbortController 映射管理
- 监听 self.addEventListener('message', e => { if (e.data === 'abort-all') controller.abort() }),由主线程触发清理
- 在 activate 事件中调用 self.clients.matchAll().then(clients => clients.forEach(c => c.postMessage('cleanup'))),通知页面释放资源










