跳转到内容

Service Worker · API

Background Sync API

发布于 更新于

有限可用不支持的浏览器: Firefox (Desktop)、Firefox (Android)、Safari (macOS)、Safari (iOS)WICG 草案

Background Sync API 让页面在 service worker 注册上登记一个带名字的 sync;当浏览器判定设备恢复连接时,它会在 service worker 中触发 sync 事件,即使发起登记的页面早已关闭。它是 Chromium 独有的能力(Chrome 49、Edge 79、Samsung Internet 5.0),Firefox 与 Safari 均未实现(BCD api.SyncManager),因此所有依赖它的重试都必须准备一条不依赖它的回退路径。

Background sync 流程图:请求在离线时失败,页面把它存入 IndexedDB 并调用 sync.register('outbox');浏览器保留该 tag 直到网络恢复,然后在 service worker 中触发 sync 事件,worker 在 event.waitUntil() 内重放队列;Promise 兑现则清除注册,lastChance 为 false 的拒绝会稍后按退避重试,lastChance 为 true 的拒绝则不再重试。
Background sync:排队、注册、在 sync 事件中重放,以及 Promise 落定的三种结果。
// 页面或 service worker 中(Chromium 要求存在一个 window 客户端)
const registration = await navigator.serviceWorker.ready;
await registration.sync.register(tag);
const tags = await registration.sync.getTags();
// service worker 中
self.addEventListener('sync', (event) => {
event.waitUntil(doWork(event.tag, event.lastChance));
});

register() 返回 Promise<void>,在注册写入存储后兑现。对一个仍在等待的 tag 重复注册不会报错,只是无操作,所以页面可以在每次请求失败时直接调用,无需自己记账。getTags() 返回 Promise<string[]>,列出尚未成功触发的 tag。

该方法接受一个字符串参数;事件对象暴露两个只读属性。

名称 类型 说明
tag string 标识这次注册。每个 tag 同时只存在一条待处理注册;同一个 tag 会以 SyncEvent.tag 送达。Chromium 限制 tag 最长 10,240 个字符。
SyncEvent.tag string 浏览器此次触发所对应的 tag。一个 worker 处理多条队列时据此分支。
SyncEvent.lastChance boolean 为 true 表示若 waitUntil() 的 Promise 再被拒绝,浏览器不会再重试。此时应把失败告知用户,而不是继续重试。

register() 以拒绝的 Promise 报错,不会同步抛出。

  • InvalidStateError:注册上还没有处于 active 状态的 service worker。先 await navigator.serviceWorker.ready 再调用 register() 即可避免(规范 “register(tag)” 第 3 步)。
  • InvalidAccessError:在 Chromium 中,从一个没有任何 window 客户端的 service worker 调用了 register(),或 tag 超出长度上限。Chromium 的报错原文是 Attempted to register a sync event without a window or registration tag too long.。
  • NotAllowedError:站点的“后台同步”权限被屏蔽。Chromium 在 chrome://settings/content/backgroundSync 与站点权限面板中暴露该开关;拒绝信息为 Permission denied.。
  • TypeError:Firefox 与 Safari 中 registration.sync 为 undefined,registration.sync.register() 在产生任何 Promise 之前就会抛出。用 'sync' in registration 做特性检测。

waitUntil() 的 Promise 被拒绝不会在页面侧表现为异常:浏览器会安排重试(Chromium:最多 3 次尝试,第二次在 5 分钟后,退避系数为 3),并在最后一次把 lastChance 置为 true。

两个示例都假定已在 /sw.js 注册了 scope 覆盖当前页面的 service worker。

把失败的 POST 入队,并在 sync 事件中重放

Section titled “把失败的 POST 入队,并在 sync 事件中重放”

页面把待发送的请求写入 IndexedDB(这里假定有一个类似 idb-keyval 的极简封装)并注册 tag;事件触发后由 worker 清空队列。队列必须放在存储里而不是 worker 的变量中:从请求失败到 sync 事件触发之间,浏览器可能已经终止过这个 worker。

page.js
async function sendOrQueue(payload) {
try {
await fetch('/api/messages', { method: 'POST', body: JSON.stringify(payload) });
} catch {
await outbox.add(payload); // 基于 IndexedDB 的队列
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('outbox');
}
}
// sw.js
self.addEventListener('sync', (event) => {
if (event.tag !== 'outbox') return;
event.waitUntil(drainOutbox(event.lastChance));
});
async function drainOutbox(lastChance) {
for (const item of await outbox.all()) {
const response = await fetch('/api/messages', { method: 'POST', body: JSON.stringify(item.payload) });
if (!response.ok && !lastChance) throw new Error(`replay failed: ${response.status}`);
await outbox.remove(item.id);
}
}

在 drainOutbox() 中抛错会让 waitUntil() 的 Promise 被拒绝,这正是向浏览器申请重试的方式。到了 lastChance 这一轮,上面的代码不再抛错并清空队列,让注册结束;生产环境的应用应把这些条目标记为失败,在下次启动时展示给用户。

检测支持并在不支持时改用别的方式重试

Section titled “检测支持并在不支持时改用别的方式重试”

Firefox 与 Safari 的注册对象上没有 sync 属性。下面的回退在 online 事件(各浏览器均会触发)时重试一次,否则留到下次页面加载再处理。

async function deferOrRetry(tag, retryNow) {
const registration = await navigator.serviceWorker.ready;
if ('sync' in registration) {
return registration.sync.register(tag);
}
// 没有 Background Sync:页面一察觉网络恢复就立即重试。
window.addEventListener('online', () => retryNow(), { once: true });
}

回退只在页面打开期间有效,这正是 Background Sync 所补上的能力缺口;应明确告知用户损失了什么(“下次打开应用时会发送你的消息”),而不是掩盖它。

规范

规范状态
Background Sync(后台同步)WICG 草案
  • 图例
  • 支持
  • 部分支持
  • 需开启标志
  • 不支持
  • 未知
浏览器 / 平台支持版本置信度来源备注
Chrome (Desktop)支持49高来源—
Chrome (Android)支持49高来源1
Edge (Desktop)支持79高来源2
Firefox (Desktop)不支持—高来源3
Firefox (Android)不支持—高来源45
Safari (macOS)不支持—高来源6
Safari (iOS)不支持—高来源78
Samsung Internet支持5.0高来源9
WebView (Android)不支持—高来源10
  1. 由 browser-compat-data 镜像自 Chrome 的数据推导。
  2. 由 browser-compat-data 镜像自 Chrome 的数据推导。
  3. browser-compat-data 未记录 Firefox 的支持。
  4. browser-compat-data 未记录 Firefox for Android 的支持。
  5. 由 browser-compat-data 镜像自 Firefox 的数据推导。
  6. 实现跟踪:https://webkit.org/b/182565。
  7. 实现跟踪:https://webkit.org/b/182565。
  8. 由 browser-compat-data 镜像自 Safari 的数据推导。
  9. 由 browser-compat-data 镜像自 Chrome Android 的数据推导。
  10. 实现跟踪:https://crbug.com/40449796。

源数据: /compatibility/background-sync.json · 全球使用占比: 72 % (StatCounter 2026-05)

来源: 规范 · MDN · 最近核验 2026-10-03 · 置信度: 高 (由来源计算)