# Background sync：重连后重试失败的请求

> Background Sync API 如何让 service worker 把失败的请求推迟到设备恢复连接后重试，它的注册与事件 API，以及执行时长限制。

import CompatTable from '@components/CompatTable.astro';
import Figure from '@components/Figure.astro';
import backgroundSyncDiagram from '@assets/diagrams/background-sync.svg';

**一句话：** Background sync（后台同步）让 web 应用的 service worker 推迟某个动作——例如发送一条
排队中的消息——直到浏览器判断设备重新获得网络连接，而不是在网络不可用时直接让它失败。

<Figure src={backgroundSyncDiagram} alt="Background sync 流程图：请求在离线时失败，页面把它存入 IndexedDB 并调用 sync.register('outbox')；浏览器保留该 tag 直到网络恢复，然后在 service worker 中触发 sync 事件，worker 在 event.waitUntil() 内重放队列；Promise 兑现则清除注册，lastChance 为 false 的拒绝会稍后按退避重试，lastChance 为 true 的拒绝则不再重试。" caption="Background sync：排队、注册、在 sync 事件中重放，以及 Promise 落定的三种结果。" />

## 它做什么

当某次 fetch 因设备离线而失败时，页面可以注册一个同步请求，而不是直接放弃。浏览器会保留这个注册，
一旦它判断设备已恢复连接，就会在 service worker 中触发 `sync` 事件，让应用重试这项被推迟的工作——
即便最初注册它的页面早已关闭。

## 注册与事件 API

该 API 通过 `ServiceWorkerRegistration.sync` 访问，它返回一个 `SyncManager`：

```js
async function queueMessage() {
  try {
    await sendMessage();
  } catch {
    const registration = await navigator.serviceWorker.ready;
    if ('sync' in registration) {
      await registration.sync.register('send-queued-message');
    }
  }
}
```

- **`SyncManager.register(tag)`** 使用给定的 tag 注册一个一次性同步请求，返回的 Promise 在注册
  完成后 resolve。
- **`SyncManager.getTags()`** resolve 为当前已注册的 tag 列表。

在 service worker 中，应用监听 `sync` 事件，并把重试工作的 Promise 传给 `waitUntil()`，
将事件的生命周期延长。用户代理仍可对这段延长的时间施加自己的执行与生命周期限制，因此
`waitUntil()` 并不保证工作一定完成——参见下方"常见问题"：

```js
self.addEventListener('sync', (event) => {
  if (event.tag === 'send-queued-message') {
    event.waitUntil(sendQueuedMessage());
  }
});
```

## 跨浏览器的可用性

Background sync 是 Chromium（Blink）的特性——Chrome、Edge 和三星 Internet 都已实现，而 WebKit
（Safari）和 Gecko（Firefox）尚未实现。

<CompatTable feature="background-sync" />

更多细节见[兼容性页面](/zh/compatibility/background-sync/)。

## 常见问题

- 根据规范，当注册它的页面或 worker 仍在运行时，事件会在连接恢复可用后触发——但不一定是立即
  触发。如果该上下文已不再运行，用户代理则只被期望在其"最方便的时机"触发该事件，而不是按任何
  有保证的时间表。
- 规范允许用户代理对 `SyncEvent` 施加自己的执行时长与生命周期限制——调用 `event.waitUntil()` 并不
  保证浏览器会让任意长的工作在终止 worker 之前完成。
- 截至目前，Background sync 在 WebKit（Safari）和 Gecko（Firefox）中不受支持——始终把这种重试
  设计为一种增强，而不是成功的唯一路径。
- 根据规范，失败的事件可能会依据用户代理自定的启发式规则重试，除非它是最后一次机会——此时不再
  重试。重试并不保证最终一定成功，这也不是一个周期性的计划任务。

## 如何在运行时检测

在注册 tag 之前先检测 `SyncManager`，并在它不可用时回退为立即重试（或在下次加载页面时重试）：

```js
function supportsBackgroundSync(registration) {
  return 'sync' in registration;
}

async function deferOrRetry(registration, tag, retryNow) {
  if (!supportsBackgroundSync(registration)) {
    return retryNow(); // 没有 Background Sync：改为立即重试
  }
  return registration.sync.register(tag);
}
```

## 下一步

- [Background sync：浏览器支持情况](/zh/compatibility/background-sync/) — 浏览器支持矩阵
- [Periodic background sync](/zh/reference/service-worker/periodic-background-sync/) —
  一个相关但独立的 API