# 用于内容更新的周期性后台同步

> 周期性后台同步何时是刷新内容的正确工具、Chrome 如何以安装和参与度为闸门、minInterval 承诺了什么，以及如何测试它。

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

周期性后台同步让已安装的 PWA 按浏览器选择的节奏刷新缓存内容：在没有页面打开时，于 Service Worker 内触发 `periodicsync` 事件。它存在的目的是让新闻或天气应用打开时显示最新数据而不是加载圈。它仅限 Chromium，并以安装和用户实际打开应用的频率为闸门。

<Figure src={periodicSyncDiagram} alt="周期性后台同步流程图：一个具备足够站点参与度的已安装 PWA 检查 periodic-background-sync 权限，并以 tag 与 minInterval 调用 periodicSync.register()；浏览器以不高于 minInterval 的频率安排同步，在时间到达且设备在线时于 Service Worker 内触发 periodicsync 事件，worker 在 event.waitUntil() 内抓取并缓存新数据。该循环持续到应用被卸载、参与度衰减或调用 periodicSync.unregister() 为止。" caption="周期性后台同步：从权限与注册到反复触发的 periodicsync 事件。" />

## 工作原理

页面用 `registration.periodicSync.register(tag, { minInterval })` 注册一个 tag；随后浏览器在设备在线时以不高于 `minInterval` 的频率在 Service Worker 内触发 `periodicsync`，且只在它认为应用值得唤醒的期间持续。该 API 表面（`PeriodicSyncManager`、`register()`、`getTags()`、`unregister()`、`periodicsync` 事件）存在于 Chrome 80 和 Edge 80，其他地方都没有（BCD `api.PeriodicSyncManager`）；方法级参考见 [Service Worker 条目](/zh/reference/service-worker/periodic-background-sync/)。

### Chrome 的闸门

Chrome 只为用户已安装并以独立应用方式启动过的 Web App 触发该事件，不为标签页中的站点触发，并且只在设备连接到此前用过的网络时触发（[Periodic background sync](https://developer.chrome.com/docs/capabilities/periodic-background-sync)，developer.chrome.com）。频率与 Chrome 为每个源维护的站点参与度得分挂钩，可在 `about://site-engagement/` 查看：得分为零意味着完全没有事件，得分越高允许的同步越接近 `minInterval`。用户不再打开应用，得分衰减，事件随之停止。因此 `minInterval` 是下限而不是时间表，`minInterval: 0` 的注册把节奏完全交给 Chrome。

### 权限

该特性由 `periodic-background-sync` 权限把关，Chrome 对已安装应用自动授予且不弹提示。`navigator.permissions.query({ name: 'periodic-background-sync' })` 在窗口或 Service Worker 中都报告 `granted` 或 `denied`；`register()` 在权限被拒时以 `NotAllowedError` 拒绝，注册没有活跃 worker 时以 `InvalidStateError` 拒绝，从非顶级浏览上下文的窗口调用时以 `InvalidAccessError` 拒绝（[PeriodicSyncManager: register()](https://developer.mozilla.org/en-US/docs/Web/API/PeriodicSyncManager/register)，developer.mozilla.org）。

### 适合什么，不适合什么

后台同步（Background Sync）虽然名字相近，但做的是在网络恢复后重试失败的请求；周期性后台同步是按节奏拉取新数据。Web Push 也能唤醒 worker，但会用通知打断用户，而且必须在 `userVisibleOnly` 之下这样做。周期性同步是唯一安静且重复的选项，这让它适合每日文章缓存，而不适合任何必须在特定时刻发生的事情；后者没有已发布的 API，[通知触发器](/zh/reference/notifications/notification-triggers/)条目解释了原因。

## 示例

两个示例都保留页面加载时的刷新作为兜底，并让 API 在可用的地方替代它。

### 在应用具备条件时注册每日文章刷新

在 Service Worker 活跃且权限已授予之后注册。`catch` 覆盖 Chrome 的标签页场景和所有其他引擎，代码退回到加载时刷新。

```js
async function enableDailyRefresh() {
  const registration = await navigator.serviceWorker.ready;
  if (!('periodicSync' in registration)) {
    return refreshOnLoad(); // Firefox、Safari，或未安装的 Chrome 标签页
  }
  const status = await navigator.permissions.query({ name: 'periodic-background-sync' });
  if (status.state !== 'granted') return refreshOnLoad();
  try {
    await registration.periodicSync.register('update-articles', { minInterval: 24 * 60 * 60 * 1000 });
  } catch {
    refreshOnLoad(); // 权限被拒或没有活跃 worker：行为与以前一致
  }
}
```

`refreshOnLoad()` 是原有行为，没有该 API 的引擎不会失去任何东西；API 只是在可用的地方去掉了加载圈。

### 处理事件并跳过加载时刷新

worker 在 `event.waitUntil()` 内缓存响应；页面检查 `getTags()`，同步已注册时跳过自己的刷新，因为缓存已经是新的。

```js
// Service Worker
self.addEventListener('periodicsync', (event) => {
  if (event.tag === 'update-articles') {
    event.waitUntil(caches.open('articles').then((cache) => cache.add('/api/articles')));
  }
});

// 页面
async function maybeRefresh(registration) {
  const tags = 'periodicSync' in registration ? await registration.periodicSync.getTags() : [];
  if (!tags.includes('update-articles')) refreshOnLoad();
}
```

处理函数要小：Chrome 给 worker 的时间窗有限，一次在计费网络上下载数兆字节的同步正是会降低参与度预算的那种行为。

:::observed
Chrome DevTools 的 Application > Background services > Periodic background sync 有一个 **Start recording** 按钮；按下后录制保持最多三天，并列出每次 `register`、`periodicsync` 派发和 `unregister` 及其 tag 与时间戳，这是看到 Chrome 究竟何时选择触发事件的唯一方式（[Periodic background sync](https://developer.chrome.com/docs/capabilities/periodic-background-sync)，developer.chrome.com）。同一指南指向 `about://site-engagement/`，其中列出每个源的得分；得分为 `0` 的已安装应用可以成功注册，但收不到任何事件。
:::

## 另请参阅

- [Web Periodic Background Synchronization](https://wicg.github.io/periodic-background-sync/)（wicg.github.io）
- [Periodic background sync](https://developer.chrome.com/docs/capabilities/periodic-background-sync)（developer.chrome.com）
- [PeriodicSyncManager.register()](/zh/reference/service-worker/periodic-background-sync/)
- [后台同步](/zh/reference/service-worker/background-sync/)
- [Web Push 与 PushManager.subscribe()](/zh/reference/notifications/web-push/)
- [通知触发器（showTrigger）](/zh/reference/notifications/notification-triggers/)