service worker 的 background sync 不支持精确的“网络空闲时触发”,但可通过 indexeddb 持久化数据、sync 事件中预检网络状态、并发控制、指数退避重试及 periodic sync 兜底,间接实现资源宽松时的可靠同步。

Service Worker 的 Background Sync 本身不提供“网络空闲时触发”的精确控制能力,它没有直接监听带宽占用、CPU 负载或“空闲”状态的 API。浏览器内部会基于网络可用性、设备电量、连接类型(如是否在蜂窝网络)、任务优先级等综合调度 sync 事件,但开发者无法主动指定“等网络变空闲再执行”。不过,你可以通过合理设计同步逻辑与数据管理策略,**间接实现类似效果:让失败的数据在真正适合的时机(比如网络恢复且系统资源宽松)被异步恢复**。
确保原始数据可靠持久化
同步失败的前提是数据不能丢。所有待同步的原始数据(如表单内容、上传文件元信息、日志条目)必须在主线程中存入 IndexedDB 或 Cache API,而不是仅保留在内存或 localStorage 中。
- IndexedDB 支持结构化数据和事务,适合存储带时间戳、状态、重试次数的请求对象
- 每条记录建议包含:id、url、method、body(序列化)、headers、createdAt、retryCount、lastAttemptAt
- 提交操作需先写库成功,再注册 sync 任务;避免“注册了却没存住数据”
用 sync 事件触发异步恢复逻辑
在 Service Worker 的 sync 事件处理器中,不要硬编码发起 fetch,而是启动一个可中断、可重试、带节流判断的恢复流程:
- 通过 event.tag 匹配对应类型的任务(例如 'recover-failed-uploads')
- 调用 event.waitUntil() 包裹一个 Promise,该 Promise 内部读取 IndexedDB 中状态为 'pending' 或 'failed' 的记录
- 对每条记录做轻量预检:检查当前 navigator.onLine 是否为 true、fetch 是否可用、甚至用 navigator.connection.effectiveType 判断是否为 '4g' 或更好(可选)
- 使用 Promise.allSettled() 并行处理多条,但限制并发数(如最多 3 个),避免瞬间占满连接
失败后智能重试,而非立即重注册
Background Sync 不保证一次成功,也不自动重试。你需要自己控制重试节奏,避免雪崩或耗电:
- 每次同步失败后,在 IndexedDB 中更新该条记录的 retryCount 和 lastAttemptAt
- 根据失败原因决定是否重注册:服务端 5xx 可延后重试;4xx(如参数错误)应标记为永久失败;网络超时可指数退避(如 1s → 5s → 30s)
- 重注册前加防抖:若上次失败距今不足 30 秒,跳过本次注册;防止页面频繁交互导致重复触发
- 注册时仍用相同 tag,这样下次 sync 触发时能继续处理这批数据
配合周期性同步(Periodic Sync)补充兜底
虽然一次性 sync 是主流,但 Periodic Sync(需 manifest.json 配置 + 用户授权)可在长时间离线后定期唤醒,检查是否有积压未恢复的数据:
- 在 manifest 中声明 "periodicSync": { "minInterval": 120000 }(至少 2 分钟)
- 主页面调用 registration.periodicSync.register() 请求周期任务
- Service Worker 监听 periodicsync 事件,只做“扫描+触发一次 recover”动作,不直接发请求
- 它不替代 one-shot sync,而是作为后台健康检查机制,提升最终一致性










