OpenPWAStore
返回 News
Guide · May 19, 2026

后台同步是离线就绪工作流,不只是"稍后重试

后台同步的实际 serviceworker 模式,让 PWA 在离线场景发生前就做好准备,而不是事后补救。

OpenPWA Editorial1 min read
后台同步是离线就绪工作流,不只是"稍后重试 cover

为什么后台同步需要工作流,而不只是 API 调用

后台同步常被误解为"网络恢复后魔法般修复失败的请求"。实际上,它需要一套明确的工作流:排队操作、保存副本、并在同步执行时将结果推送到客户端。没有这个三段式设计,要么静默失败,要么让应用进入不一致状态。

同步事件本身只保证"浏览器可能会运行你的 serviceworker",而不是"一定会处理完所有待办事项,并把结果通知给页面"。同步工作流是操作真正发生与"看起来发生了"的差距。

构建同步优先的工作流,而非同步作为补救措施

把后台同步纳入每一次操作的正常流程,而不是失败后的补丁:

  1. 请求前/同时: 将操作写入 IndexedDB,状态标记为等待中。
  2. 发起请求: 立即尝试网络 fetch。
  3. 请求成功: 将操作状态更新为完成,并清理队列。
  4. 请求失败: 针对该操作类型注册后台同步事件。
  5. 同步处理中: serviceworker 从 IndexedDB 读取待办操作,逐一处理并标记完成。
  6. 通知客户端: 使用 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 配额耗尽。
  • [ ] 在不支持同步的浏览器上,将网络失败直接暴露给用户,而不是假装会自动重试。

##这对安装的用户意味着什么

安装后,用户对"断网也要能正常工作"的期望自然更高。后台同步只是其中一环,它要与即时的离线反馈配合:展示什么在排队中、同步状态何时变化,并在同步永久失败时让用户手动重试。

要把同步当作数据模型的一部分,而不是网络错误的补丁。当队列是一条真理,无论在线、离线还是中间状态,应用始终保持一致。