跳转到内容

Service Worker · API

Periodic Background Sync API

发布于

Periodic Background Sync API 让已安装的 web 应用注册一个带名字的任务,由浏览器按自己选择的时机在 service worker 中运行,频率不高于应用要求的 minInterval,这样用户下次打开应用时内容已经是新的。它自 Chrome 80、Edge 80、Samsung Internet 13 起可用,Firefox 与 Safari 均未实现(BCD api.PeriodicSyncManager),而且 Chrome 只把它授予已安装、且站点参与度分数大于零的应用。

周期性后台同步流程图:已安装且有足够参与度的 PWA 检查 periodic-background-sync 权限,并以 tag 与 minInterval 调用 periodicSync.register();浏览器按不高于 minInterval 的频率排期,到时且设备在线时在 service worker 中触发 periodicsync 事件,worker 在 event.waitUntil() 内拉取并缓存新数据;循环持续到应用被卸载、参与度衰减或调用 periodicSync.unregister()。
周期性后台同步:从权限与注册到反复触发的 periodicsync 事件。
// 页面或 service worker 中
const registration = await navigator.serviceWorker.ready;
await registration.periodicSync.register(tag, { minInterval });
const tags = await registration.periodicSync.getTags();
await registration.periodicSync.unregister(tag);
// service worker 中
self.addEventListener('periodicsync', (event) => {
event.waitUntil(refresh(event.tag));
});

register() 在注册写入存储后返回 Promise<undefined>;对已存在的 tag 再次注册会替换它的 minInterval。getTags() 兑现为所有仍在注册中的 tag,unregister() 无论 tag 是否存在都兑现为 undefined。periodicsync 事件只携带 tag;与一次性后台同步不同,它没有 lastChance,因为一次失败的运行之后只是等待下一次排期。

该方法接受一个 tag 与一个选项对象。

名称 类型 说明
tag string 标识这次注册,并以 PeriodicSyncEvent.tag 送达。每个 tag 一条注册;多个 tag 可以按不同节奏运行。
options.minInterval number 两次运行之间的最小毫秒数。浏览器把它当作下限而不是时间表:Chrome 让实际频率与应用的使用频率对齐,同时考虑设备的电量与网络状态,参与度分数为零时则完全跳过。

Chrome 还要求存在一个此前用过的网络才会触发事件,因此设备连上从未见过的网络时,用户在该网络上打开一次应用之前不会同步。

register() 在三种情况下拒绝(MDN,PeriodicSyncManager.register())。

  • InvalidStateError(DOMException):注册上没有 active 状态的 service worker。先 await navigator.serviceWorker.ready。
  • NotAllowedError(DOMException):periodic-background-sync 权限未授予。在 Chrome 中,任何普通浏览器标签页都处于这个状态,因为该权限只授予已安装的应用;同样的代码在应用从自己的窗口启动后就能成功。
  • InvalidAccessError(DOMException):调用方窗口不是顶层或辅助浏览上下文,例如 iframe。
  • TypeError:Firefox 与 Safari 中 registration.periodicSync 为 undefined,访问其成员本身就会抛出。用 'periodicSync' in registration 做特性检测。

示例共用位于 /sw.js 的一个 service worker,以及名为 news 的新闻缓存。

页面在调用 register() 之前先查询权限,未安装的应用因此不会撞上 NotAllowedError,而是改为向用户显示“打开时刷新”的提示。

async function enableDailyRefresh() {
const status = await navigator.permissions.query({ name: 'periodic-background-sync' });
if (status.state !== 'granted') {
showNotice('安装应用后,头条会在后台自动刷新。');
return;
}
const registration = await navigator.serviceWorker.ready;
await registration.periodicSync.register('news-refresh', {
minInterval: 24 * 60 * 60 * 1000,
});
}

Firefox 与 Safari 对未知权限名的 permissions.query() 会抛出 TypeError,跨浏览器的调用方应把查询包在 try/catch 中,并把失败视为“未授予”。

worker 拉取并存储新闻;抛出的错误会让 waitUntil() 的 Promise 被拒绝,浏览器记录后忽略,直到下一次排期。

self.addEventListener('periodicsync', (event) => {
if (event.tag !== 'news-refresh') return;
event.waitUntil((async () => {
const response = await fetch('/api/headlines', { cache: 'no-store' });
if (!response.ok) throw new Error(`headlines: ${response.status}`);
const cache = await caches.open('news');
await cache.put('/api/headlines', response);
})());
});

工作量要短:Chrome 在自己的执行时限到达后结束事件,仍在运行的 worker 会被终止,缓存写入也随之中断。

检测支持并改为返回页面时刷新

Section titled “检测支持并改为返回页面时刷新”

periodicSync 缺失或权限未授予时,回退方案在页面重新可见时刷新,对普通标签页或未安装的应用同样达成“打开即新”的目标,代价是一次可见的网络等待。

async function setUpRefresh(refreshNow) {
const registration = await navigator.serviceWorker.ready;
if ('periodicSync' in registration) {
try {
await registration.periodicSync.register('news-refresh', { minInterval: 24 * 60 * 60 * 1000 });
return;
} catch {
// 普通标签页中会被拒绝:落入前台回退。
}
}
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') refreshNow();
});
refreshNow();
}

回退只在页面存在期间运行,无法为已关闭的应用刷新内容,这正是该 API 所填补的空缺。

规范

规范状态
无。