不可靠,sync事件仅承诺联网且SW活跃时尽可能调度一次任务;需自行维护IndexedDB队列、显式waitUntil、串行重试并处理版本升级与配额限制。

后台同步 API 在弱网下真的可靠吗?
不可靠,sync 事件本身不保证立即触发,也不保证一定成功。它只承诺:当设备联网且浏览器处于活跃或后台运行状态(非完全关闭)时,尽可能调度一次同步任务。Chrome 要求用户至少曾与站点交互过(如点击、提交表单),否则 navigator.serviceWorker.ready.then(r => r.sync.register('my-tag')) 会直接抛出 "InvalidStateError: Registration failed - no active window"。
常见误判是把它当“离线队列+自动重试”,实际它更像“联网后的一次性唤醒钩子”。真正要实现最终一致性,必须自己维护待同步队列,并在 sync 回调里主动消费队列。
如何安全注册 sync 并避免重复触发
注册前必须确保 Service Worker 已激活,且同步 tag 名需全局唯一——同一 tag 多次注册不会报错,但后续注册会覆盖前一次,且浏览器可能合并多次 pending 的 sync 请求为单次触发。
-
synctag 命名建议带业务上下文,比如'submit-order-v2'而非泛用的'sync',便于调试和灰度控制 - 注册应放在用户明确发起操作之后(如表单提交成功但接口返回 5xx),而非页面加载时盲目注册
- 注册失败时捕获
DOMException,检查err.name是否为'InvalidStateError'或'NotAllowedError',前者说明 SW 未就绪,后者通常因用户未与页面交互过(可提示“请先点击页面任意位置再试”)
sync 事件中怎么重试失败请求并更新本地状态
sync 事件回调里不能直接访问 DOM 或 window,所有数据必须提前存入 IndexedDB。典型流程是:离线时把请求参数、时间戳、重试次数写入 DB;sync 触发后读取未完成项,逐个 fetch,成功则删除记录,失败则更新重试次数并设置退避延迟(如指数退避)。
关键细节:
- fetch 必须显式设置
mode: 'no-cors'仅适用于简单请求(GET/POST + text/plain),若需携带凭证或自定义 header,必须用mode: 'cors'且后端支持跨域,否则静默失败 - 务必用
event.waitUntil(promise)包裹整个处理逻辑,否则浏览器可能在 promise resolve 前终止 SW 线程 - 避免在单次 sync 中发起过多并发请求,弱网下易触发连接限制;建议串行或限制并发数(如
Promise.allSettled(chunk)分批)
self.addEventListener('sync', event => {
if (event.tag === 'submit-order-v2') {
event.waitUntil(
idbOpen('mydb').then(db => readPendingOrders(db))
.then(orders => Promise.allSettled(
orders.map(order => fetch('/api/order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(order.payload)
}).then(r => {
if (!r.ok) throw new Error(r.status);
return deleteFromIDB(order.id); // 成功才删
}))
))
);
}
});为什么 IndexedDB 比 localStorage 更适合做同步队列
localStorage 是同步阻塞 API,在 SW 线程中调用会导致主线程卡顿,且不支持事务和大对象存储;而 IndexedDB 是异步、支持事务、能存二进制和嵌套结构,还可在不同上下文(页面、SW、Web Worker)间共享。
容易被忽略的是版本升级问题:首次打开页面时若 SW 尝试打开一个不存在的 DB,需在 onupgradeneeded 中创建 objectStore;后续修改字段结构(如新增 retryCount 字段)必须 bump version 并迁移旧数据,否则 get() 可能返回 undefined。
另外,弱网下频繁读写 IDB 可能触发 QuotaExceededError,建议对单条记录大小做限制(如 ≤1MB),超长 payload 应先压缩或分片。

















