5月28日 01:41

Service Worker Background Sync 是什么?怎么用?

Service Worker Background Sync 是什么?

Background Sync 是 Service Worker 提供的一种延迟任务机制——当用户离线时发起的操作(如表单提交、消息发送),会在网络恢复后自动重试,用户不需要停留在页面上,也不需要手动操作。

它解决的核心痛点很简单:离线操作不应该丢失。传统做法是监听 online 事件再重试,但用户可能已经关掉了页面。Background Sync 让这个重试逻辑跑在 Service Worker 里,即使用户离开了网站也能执行。

怎么用?三步走

第一步:主页面注册 sync 事件

javascript
async function submitForm(data) { // 先把数据存到 IndexedDB await saveToIndexedDB('outbox', data); const registration = await navigator.serviceWorker.ready; try { await registration.sync.register('sync-forms'); } catch { // 不支持 Background Sync,走降级逻辑 if (navigator.onLine) await sendToServer(data); } }

关键点:sync.register(tag) 的 tag 是同步事件的唯一标识。同一个 tag 重复注册不会创建多个事件,而是合并。这意味着你不需要担心重复提交的问题——浏览器会帮你去重。

第二步:Service Worker 中监听 sync 事件

javascript
// sw.js self.addEventListener('sync', event => { if (event.tag === 'sync-forms') { event.waitUntil(doSync()); } }); async function doSync() { const items = await getAllFromIndexedDB('outbox'); for (const item of items) { const res = await fetch('/api/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(item), }); if (res.ok) { await removeFromIndexedDB('outbox', item.id); } else { throw new Error('同步失败'); // 抛出错误才会触发重试 } } }

这里有个容易被忽略的细节:必须抛出错误才能触发重试。如果你 catch 了错误然后默默吞掉,浏览器会认为同步成功,不会重试。event.waitUntil() 接收的 Promise 如果 reject,浏览器会按照指数退避策略重试(最多3次)。

第三步:IndexedDB 持久化

Background Sync 不负责存储数据,你必须在注册 sync 之前把待发送的数据写入 IndexedDB。Service Worker 无法访问 localStorage,IndexedDB 是唯一选择。

javascript
function openDB() { return new Promise((resolve, reject) => { const req = indexedDB.open('sync-db', 1); req.onupgradeneeded = e => { const db = e.target.result; if (!db.objectStoreNames.contains('outbox')) { db.createObjectStore('outbox', { keyPath: 'id' }); } }; req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); }); } async function saveToIndexedDB(store, data) { const db = await openDB(); return new Promise((resolve, reject) => { const tx = db.transaction(store, 'readwrite'); tx.objectStore(store).put(data); tx.oncomplete = () => resolve(); tx.onerror = () => reject(tx.error); }); }

重试机制是怎么回事?

Background Sync 的重试策略是浏览器内置的,开发者无法自定义重试间隔。大致规则:

  • 首次失败后,会在短时间内重试
  • 后续重试间隔逐渐增大(指数退避)
  • 最多重试 3 次
  • 3 次都失败后,这个 sync 事件会被丢弃

这也意味着你的同步逻辑必须是幂等的——同一份数据提交多次不应该产生重复记录。服务端需要做去重处理(比如用 id 判重),或者客户端在发送时带唯一标识。

Periodic Background Sync 又是什么?

Background Sync 是"用户触发、离线延迟执行"。Periodic Background Sync 是"浏览器自动触发、定期执行",比如每天同步一次最新数据。

javascript
// 注册定期同步 const registration = await navigator.serviceWorker.ready; await registration.periodicSync.register('daily-content', { minInterval: 24 * 60 * 60 * 1000, // 最少间隔 24 小时 }); // Service Worker 处理 self.addEventListener('periodicsync', event => { if (event.tag === 'daily-content') { event.waitUntil(fetchLatestContent()); } });

Periodic Sync 的限制更严格:必须是在 minInterval 指定的时间之后才可能触发,而且浏览器会根据用户使用频率决定是否真的触发。如果一个网站用户一周都没访问过,Periodic Sync 大概率不会执行。

Background Sync vs Background Fetch

这两个 API 经常被混淆,区别很明确:

维度Background SyncBackground Fetch
数据量小(表单、消息)大(文件上传下载)
用户感知静默执行显示进度条
权限要求需要用户授权
进度回调
典型场景离线表单提交离线下载大文件

简单判断:数据量小、不需要显示进度 → Sync;数据量大、需要显示进度 → Fetch。

浏览器兼容性——这是最大的坑

截至 2026 年:

浏览器Background SyncPeriodic Sync
Chrome支持支持
Edge支持支持
Firefox不支持不支持
Safari不支持不支持

Firefox 和 Safari 至今不支持 Background Sync。这意味着如果你的用户群在 iOS 或 Firefox 上,必须提供降级方案。

降级思路:检测 registration.sync 是否存在,不存在就走 online 事件监听 + 页面内重试的老路。

javascript
async function syncWithFallback(tag) { const registration = await navigator.serviceWorker.ready; if ('sync' in registration) { await registration.sync.register(tag); } else { // 降级:监听网络恢复 window.addEventListener('online', () => doSync()); // 如果已经在线,直接执行 if (navigator.onLine) await doSync(); } }

生产环境的注意事项

  1. 数据必须先落盘:注册 sync 之前,把数据写入 IndexedDB。Service Worker 随时可能被终止,内存里的数据靠不住。

  2. 做好去重:sync 可能重试,同一份数据可能发送多次。服务端根据唯一 ID 去重,或者用 PUT 替代 POST

  3. 给用户反馈:同步成功后用 self.registration.showNotification() 发通知,让用户知道离线操作已经完成。

  4. tag 命名要规范:不要用 'sync' 这种通用名字,用 'sync-user-form''sync-chat-message' 这种带业务语义的 tag,方便在 Service Worker 里分派不同逻辑。

  5. 不要存太多数据:IndexedDB 里的待同步队列不要无限增长。加个上限,超过就提示用户而不是静默堆积。

  6. 测试方法:Chrome DevTools → Application → Service Workers → 勾选 "Offline" 模拟离线,注册 sync 后取消勾选观察是否自动触发。

追问:Sync 事件会不会无限重试消耗电量?

不会。浏览器内置了保护机制:最多重试 3 次,间隔指数增长。3 次失败后事件被丢弃。另外,sync 事件只在浏览器认为"条件合适"时才触发(电量充足、网络稳定),不会在恶劣条件下反复尝试。

追问:一个页面能注册多少个 sync tag?

规范没有硬性上限,但浏览器有实现限制。Chrome 大约允许 100 个未完成的 sync 注册。实际开发中,应该按业务分类使用少数几个 tag(如一个表单 tag、一个消息 tag),而不是每个操作一个 tag。同 tag 的新注册会覆盖旧的,所以同一类操作用同一个 tag 就够了。

追问:页面关闭后 sync 还能执行吗?

能。这就是 Background Sync 的核心价值——sync 事件在 Service Worker 中触发,Service Worker 独立于页面运行。即使用户关掉了标签页,只要浏览器进程还在,sync 就能在网络恢复时执行。这也是为什么数据必须存 IndexedDB 而不能依赖页面内存。

标签:Service Worker