后台同步是离线就绪工作流,不只是"稍后重试
后台同步的实际 serviceworker 模式,让 PWA 在离线场景发生前就做好准备,而不是事后补救。
为什么后台同步需要工作流,而不只是 API 调用
后台同步常被误解为"网络恢复后魔法般修复失败的请求"。实际上,它需要一套明确的工作流:排队操作、保存副本、并在同步执行时将结果推送到客户端。没有这个三段式设计,要么静默失败,要么让应用进入不一致状态。
同步事件本身只保证"浏览器可能会运行你的 serviceworker",而不是"一定会处理完所有待办事项,并把结果通知给页面"。同步工作流是操作真正发生与"看起来发生了"的差距。
构建同步优先的工作流,而非同步作为补救措施
把后台同步纳入每一次操作的正常流程,而不是失败后的补丁:
- 请求前/同时: 将操作写入 IndexedDB,状态标记为等待中。
- 发起请求: 立即尝试网络 fetch。
- 请求成功: 将操作状态更新为完成,并清理队列。
- 请求失败: 针对该操作类型注册后台同步事件。
- 同步处理中: serviceworker 从 IndexedDB 读取待办操作,逐一处理并标记完成。
- 通知客户端: 使用
Client.postMessage()或 Broadcast Channel API,让打开的页面更新 UI。
最稳健的做法是把操作状态视为单一事实源。不要依赖 fetch() 的返回——记录发生了什么、什么还在待办。
使用带重试逻辑的同步队列
后台同步不会自动重试。你需要构建一个小型队列系统:
// IndexedDB 待办操作的存储设计
const SYNC_QUEUE_STORE = 'syncQueue';
async function enqueueAction(action) {
return db.add(SYNC_QUEUE_STORE, {
id: crypto.randomUUID(),
type: action.type,
payload: action.payload,
status: 'pending',
createdAt: Date.now(),
attemptCount: 0
});
}
async function markActionCompleted(actionId) {
await db.delete(SYNC_QUEUE_STORE, actionId);
}
async function getNextPendingActions(limit = 10) {
return db.getAll(SYNC_QUEUE_STORE, IDBKeyRange.lowerBound(0), limit);
}在 serviceworker 的 sync 事件中,分批处理操作并处理部分失败:
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-queue') {
event.waitUntil(processSyncQueue());
}
});
async function processSyncQueue() {
const actions = await getNextPendingActions();
for (const action of actions) {
try {
await syncAction(action);
await markActionCompleted(action.id);
} catch (error) {
action.attemptCount += 1;
if (action.attemptCount < 3) {
await db.put(SYNC_QUEUE_STORE, action); // 稍后重试
} else {
// 记录永久失败或向用户展示
}
}
}
}把同步状态推送回打开的窗口
客户端不会自动知道同步已运行,需要主动通知:
// serviceworker 中,同步完成后
self.clients.matchAll({ type: 'window' }).then(clients => {
clients.forEach(client => {
client.postMessage({ type: 'sync-completed', syncedAt: Date.now() });
});
});
// 客户端页面中
navigator.serviceWorker.addEventListener('message', (event) => {
if (event.data.type === 'sync-completed') {
refreshPendingActionsUI();
}
});这个模式让 UI 准确展示待办数量,而无需轮询服务器。
清单:后台同步就绪检查
在发布后台同步功能之前:
- [ ] 每一个操作在 fetch 之前/同时把意图写入 IndexedDB。
- [ ] serviceworker 的 sync 事件读取队列,而不是单个 fetch。
- [ ] 对部分失败有重试逻辑与尝试次数上限。
- [ ] 同步完成后向打开的客户端页面发送消息。
- [ ] 降级/兜底 UI 在同步尚未运行时展示等待中的操作。
- [ ] 已完成的操作会被清除,避免 IndexedDB 配额耗尽。
- [ ] 在不支持同步的浏览器上,将网络失败直接暴露给用户,而不是假装会自动重试。
##这对安装的用户意味着什么
安装后,用户对"断网也要能正常工作"的期望自然更高。后台同步只是其中一环,它要与即时的离线反馈配合:展示什么在排队中、同步状态何时变化,并在同步永久失败时让用户手动重试。
要把同步当作数据模型的一部分,而不是网络错误的补丁。当队列是一条真理,无论在线、离线还是中间状态,应用始终保持一致。