2015 年,我们推出了后台同步,让服务工件可以在用户连接到网络之前推迟工作。这意味着,用户可以输入消息、点击“发送”并离开网站,知道消息会立即发送或在他们有网络连接时发送。
这是一个实用的功能,但需要服务工作器在提取期间保持活跃状态。这对发送消息等短时间工作来说不会造成问题,但如果任务用时太长,浏览器将终止 Service Worker,否则就会给用户隐私和电池带来风险。
如果您需要下载可能需要很长时间的内容(例如电影、播客或游戏关卡),该怎么办?这就是后台提取的用途。
从 Chrome 74 开始,后台提取功能默认可用。
下面是一个时长两分钟的快速演示,展示了传统状态与使用后台提取功能的对比情况:
工作原理
后台提取的运作方式如下:
- 您可以指示浏览器在后台执行一组提取操作。
- 浏览器提取这些内容,并向用户显示进度。
- 提取完成或失败后,浏览器会打开您的 Service Worker 并触发一个事件来告诉您发生了什么。您可以在此处决定如何处理回答(如果有)。
如果用户在第 1 步后关闭了您网站的页面,也不用担心,下载会继续进行。由于提取操作可见性非常高并且易于取消,因此后台同步任务耗时过长的问题不会带来隐私问题。由于 Service Worker 不会持续运行,因此无需担心它会滥用系统,例如在后台挖掘比特币。
在某些平台(例如 Android)上,浏览器可能会在第 1 步之后关闭,因为浏览器可以将提取工作交给操作系统。
如果用户在离线状态下开始下载,或在下载期间离线,后台提取将暂停,并在稍后恢复。
API
功能检测
与任何新功能一样,您需要检测浏览器是否支持该功能。对于后台提取,只需执行以下操作即可:
if ('BackgroundFetchManager' in self) {
// This browser supports Background Fetch!
}
启动后台提取
主要 API 会挂接到 service worker 注册,因此请确保您已先注册 service worker。然后,执行以下操作:
navigator.serviceWorker.ready.then(async (swReg) => {
const bgFetch = await swReg.backgroundFetch.fetch('my-fetch', ['/ep-5.mp3', 'ep-5-artwork.jpg'], {
title: 'Episode 5: Interesting things.',
icons: [{
sizes: '300x300',
src: '/ep-5-icon.png',
type: 'image/png',
}],
downloadTotal: 60 * 1024 * 1024,
});
});
backgroundFetch.fetch
接受三个参数:
参数 | |
---|---|
id |
string 此后台提取的唯一标识。 如果该 ID 与某个现有的后台提取匹配,则 |
requests |
Array<Request|string>
要提取的内容。字符串将被视为网址,并通过 new Request(theString) 转换为 Request 。
只要资源允许通过 CORS 从其他来源提取内容,您就可以这样做。 注意:Chrome 目前不支持需要进行 CORS 预检的请求。 |
options |
一个对象,可能包含以下内容: |
options.title |
string 与进度一起显示的浏览器标题。 |
options.icons |
Array<IconDefinition> 包含 `src`、`size` 和 `type` 的对象数组。 |
options.downloadTotal |
number 响应正文(解压缩后)的总大小。 虽然这不是必需的,但我们强烈建议您提供。它用于告知用户下载内容的大小,并提供进度信息。如果您未提供此信息,浏览器会告知用户大小未知,因此用户可能会更有可能中止下载。 如果后台提取下载量超过此处给出的数量,系统会中止后台提取。下载小于 |
backgroundFetch.fetch
会返回一个 promise,由 BackgroundFetchRegistration
来解析。我稍后会详细介绍这一点。如果用户已选择停用下载功能,或者提供的参数之一无效,则 promise 会被拒绝。
为单次后台提取提供多个请求,可让您将对用户而言在逻辑上是单一项的操作组合在一起。例如,一部电影可以拆分为数千个资源(通常是 MPEG-DASH),并附带图片等其他资源。游戏关卡可以分布在许多 JavaScript、图片和音频资源中。但对用户而言,这只是“电影”或“关卡”。
获取现有后台提取
您可以按如下方式获取现有的后台提取内容:
navigator.serviceWorker.ready.then(async (swReg) => {
const bgFetch = await swReg.backgroundFetch.get('my-fetch');
});
…通过传递所需后台提取的 id。如果没有使用该 ID 的有效后台提取,get
会返回 undefined
。
后台提取从注册之时起,直到成功、失败或被中止,即被视为“有效”。
您可以使用 getIds
获取所有活跃后台提取的列表:
navigator.serviceWorker.ready.then(async (swReg) => {
const ids = await swReg.backgroundFetch.getIds();
});
后台提取注册
BackgroundFetchRegistration
(在上面的示例中为 bgFetch
)具有以下特点:
属性 | |
---|---|
id |
string 后台提取的 ID。 |
uploadTotal |
number 要发送到服务器的字节数。 |
uploaded |
number 成功发送的字节数。 |
downloadTotal |
number 注册后台提取时提供的值或零。 |
downloaded |
number 成功接收的字节数。 此值可能会降低。例如,如果连接中断且下载无法恢复,在这种情况下,浏览器会从头开始重新提取该资源。 |
result |
以下项之一:
|
failureReason |
以下项之一:
|
recordsAvailable |
boolean 是否可以访问底层请求/响应? 此值变为 false 后,便无法再使用 |
方法 | |
abort() |
返回 Promise<boolean> 终止后台提取。 如果提取成功终止,则返回的 promise 会解析为 true。 |
matchAll(request, opts) |
返回 Promise<Array<BackgroundFetchRecord>> 获取请求和响应。 此处的参数与缓存 API 相同。不使用参数的调用会针对所有记录返回一个 promise。 详见下文说明。 |
match(request, opts) |
返回 Promise<BackgroundFetchRecord> 与上文相同,但使用第一个匹配项进行解析。 |
事件 | |
progress |
当 uploaded 、downloaded 、result 或 failureReason 发生变化时触发。 |
跟踪进度
这可以通过 progress
事件实现。请注意,downloadTotal
是您提供的任何值,如果您未提供值,则为 0
。
bgFetch.addEventListener('progress', () => {
// If we didn't provide a total, we can't provide a %.
if (!bgFetch.downloadTotal) return;
const percent = Math.round(bgFetch.downloaded / bgFetch.downloadTotal * 100);
console.log(`Download progress: ${percent}%`);
});
获取请求和响应
bgFetch.match('/ep-5.mp3').then(async (record) => {
if (!record) {
console.log('No record found');
return;
}
console.log(`Here's the request`, record.request);
const response = await record.responseReady;
console.log(`And here's the response`, response);
});
record
是一个 BackgroundFetchRecord
,如下所示:
属性 | |
---|---|
request |
Request 提供的请求。 |
responseReady |
Promise<Response> 提取的响应。 由于系统可能尚未收到响应,因此响应会延迟。如果提取失败,promise 将拒绝。 |
Service Worker 事件
事件 | |
---|---|
backgroundfetchsuccess |
所有内容均已成功提取。 |
backgroundfetchfailure |
一个或多个提取操作失败。 |
backgroundfetchabort |
一个或多个提取操作失败。
只有在您想要清理相关数据时,此方法才非常有用。 |
backgroundfetchclick |
用户点击了下载进度界面。 |
事件对象具有以下特征:
属性 | |
---|---|
registration |
BackgroundFetchRegistration |
方法 | |
updateUI({ title, icons }) |
用于更改您最初设置的标题/图标。此为可选操作,但您可以根据需要提供更多背景信息。您只能在 backgroundfetchsuccess 和 backgroundfetchfailure 事件期间执行此操作 *一次*。 |
对成功/失败做出响应
我们已经看到了 progress
事件,但该事件仅在用户打开指向您网站的网页时才有用。后台提取的主要好处是,在用户离开页面甚至关闭浏览器后,页面会继续工作。
如果后台提取成功完成,您的服务工件将收到 backgroundfetchsuccess
事件,并且 event.registration
将是后台提取注册。
此事件发生后,您将无法再访问提取的请求和响应,因此,如果您想保留这些内容,请将其移至某个位置(例如缓存 API)。
与大多数服务工作器事件一样,请使用 event.waitUntil
,以便服务工作器知道事件何时完成。
例如,在您的服务工件中:
addEventListener('backgroundfetchsuccess', (event) => {
const bgFetch = event.registration;
event.waitUntil(async function() {
// Create/open a cache.
const cache = await caches.open('downloads');
// Get all the records.
const records = await bgFetch.matchAll();
// Copy each request/response across.
const promises = records.map(async (record) => {
const response = await record.responseReady;
await cache.put(record.request, response);
});
// Wait for the copying to complete.
await Promise.all(promises);
// Update the progress notification.
event.updateUI({ title: 'Episode 5 ready to listen!' });
}());
});
失败可能只是因为一个 404 错误,而该错误可能对您来说并不重要,因此您可能仍然需要像上面所述那样将一些响应复制到缓存中。
响应点击
显示下载进度和结果的界面是可点击的。您可以通过 Service Worker 中的 backgroundfetchclick
事件对此做出响应。如上所述,event.registration
将是后台提取注册。
与此事件相关的常见操作是打开一个窗口:
addEventListener('backgroundfetchclick', (event) => {
const bgFetch = event.registration;
if (bgFetch.result === 'success') {
clients.openWindow('/latest-podcasts');
} else {
clients.openWindow('/download-progress');
}
});
其他资源
更正:本文的早期版本错误地将后台提取称为“网络标准”。该 API 目前尚未进入标准轨道,您可以在 WICG 中以社区群组报告草稿的形式查看规范。