service worker 不直接处理超时,需由页面用 abortcontroller + settimeout 实现超时控制并触发重试;推荐分层方案:页面即时控制、indexeddb 离线队列、background sync 后台重发、服务端幂等兜底。

Service Worker 本身不直接感知“超时”,也不能主动中断一个正在 fetch 的请求来判断它是否超时。但你可以通过封装 fetch 调用 + AbortController + 定时器,在主页面或 Service Worker 内部实现带超时控制的请求,并在超时后触发重试逻辑。关键在于:超时判断和重试决策通常发生在客户端(页面脚本),而 Service Worker 更适合做离线缓存、请求拦截与持久化队列管理。
超时控制必须由发起方实现
浏览器原生 fetch 不提供 timeout 参数,必须手动控制:
- 创建 AbortController,传入 signal 到 fetch 选项中
- 用 setTimeout 在指定毫秒后调用 controller.abort()
- 捕获 AbortError 或网络错误,视为超时/失败
- 此时可选择立即重试(短间隔)、加入待重试队列、或交由 Service Worker 持久化后延后重试
Service Worker 不负责超时判断,但可接管失败后的重试调度
如果你希望“一次失败不丢数据,后续自动补发”,推荐将超时请求转为离线提交流程:
- 页面检测到 fetch 超时或网络异常(如
navigator.onLine === false或 catch 到 TypeError) - 把原始表单数据、URL、method、headers 和序列化后的 body(如 JSON 或 FormData 键值对)存入 IndexedDB
- 调用
navigator.serviceWorker.ready.then(r => r.sync.register('retry-form'))注册后台同步任务 - Service Worker 在 sync 事件中读取 IndexedDB,重建 Request 并 fetch;成功则删除记录,失败则保留并可重新注册 sync(加退避)
避免在 fetch 事件里做超时重试
Service Worker 的 fetch 事件是请求入口,不是执行重试的合适位置:
- 不能在 fetch 中 await 多次 fetch —— 浏览器要求 event.respondWith() 必须返回一个 Promise,且该 Promise 应尽快 resolve,否则影响页面加载
- 超时重试属于业务逻辑,应由页面控制节奏(比如用户点击后显示“已保存草稿,稍后提交”)
- 若强行在 fetch 里 retry,容易触发重复提交、响应延迟、甚至阻塞其他请求
端到端重试建议组合方案
真正健壮的重试不是“立刻再发三次”,而是分层处理:
- 第一层(即时):页面内用 AbortController 控制单次请求,超时后提示用户“网络不稳定,已暂存”,不刷新页面
- 第二层(离线队列):将请求元数据存 IndexedDB,支持页面关闭后仍存在
- 第三层(后台唤醒):用 Background Sync 在网络恢复时触发一次集中重发
- 第四层(服务端兜底):所有重试请求带唯一 request-id,后端幂等处理,避免重复下单或扣款










