跳转到内容

Background Fetch:不受标签页关闭影响的大文件下载

发布于

一句话: Background Fetch 提供了一种管理可能耗时较长的下载(如电影、音频文件、软件)的方法——浏览器以用户可见的方式执行这些抓取,显示进度并提供取消方式,下载完成后唤醒 service worker。

当网页应用要求用户下载大文件时,用户通常需要停留在页面上下载才能完成——关闭标签页或离开页面会中止下载。Background Fetch 让浏览器在后台执行抓取:“The browser then performs the fetches in a user-visible way, displaying progress to the user and giving them a method to cancel the download. Once the download is complete the browser then opens the service worker, at which point your application can do something with the response if required.”(浏览器随后以用户可见的方式执行抓取,向用户展示进度并提供取消方法。下载完成后浏览器会唤醒 service worker,届时应用可以在需要时处理响应。)

它也能应对连接状态变化:即使用户在离线状态下发起抓取,也会在联网后开始;若用户在抓取过程中离线,进程会暂停,直到重新联网。

这与 Background Synchronization API 不同,后者 “can’t be used for long running tasks such as downloading a large file”(无法用于诸如下载大文件之类的长时间运行任务),因为它要求 service worker 在抓取完成前保持存活,而浏览器为节省电量最终会终止该任务。

先做特性检测,再对 service worker registration 调用 backgroundFetch.fetch():

async function regularDownloadFallback(urls) {
// 不支持(或不可用):改用普通 fetch 逐个下载文件,
// 并为每个响应触发浏览器保存。
for (const url of urls) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Fallback download failed for ${url}: ${response.status}`);
}
const blob = await response.blob();
const objectUrl = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = objectUrl;
link.download = url.split("/").pop();
link.click();
URL.revokeObjectURL(objectUrl);
}
}
const filesToFetch = ["/ep-5.mp3", "ep-5-artwork.jpg"];
if (!("BackgroundFetchManager" in self)) {
regularDownloadFallback(filesToFetch);
} else {
navigator.serviceWorker.ready.then(async (swReg) => {
if (!("backgroundFetch" in swReg)) {
// Registration exists but backgroundFetch is unavailable: fall back.
await regularDownloadFallback(filesToFetch);
return;
}
const bgFetch = await swReg.backgroundFetch.fetch(
"my-fetch",
filesToFetch,
{
title: "Episode 5: Interesting things.",
icons: [
{
sizes: "300x300",
src: "/ep-5-icon.png",
type: "image/png",
},
],
downloadTotal: 60 * 1024 * 1024,
},
);
});
}

单次 fetch() 调用可以将多个文件(例如一集播客及其封面图)一起请求,作为一个用户可见的下载包。该调用返回一个 promise,解析为 BackgroundFetchRegistration。

接口 说明
ServiceWorkerRegistration.backgroundFetch 返回该 registration 对应的 BackgroundFetchManager 引用。
BackgroundFetchManager 一个映射,键为 background fetch ID,值为 BackgroundFetchRegistration 对象。
BackgroundFetchRegistration 表示单次 Background Fetch 操作。
BackgroundFetchRecord 表示一次 registration 内某个独立的抓取请求与响应。

service worker 全局作用域定义了四种 background fetch 事件类型。当一次操作结束时,backgroundfetchsuccess、backgroundfetchfail、backgroundfetchabort 三者中恰好会触发一个;backgroundfetchclick 则是独立触发的,只有当用户点击了浏览器为该操作显示的界面时才会触发:

事件 触发时机
backgroundfetchsuccess 一次 background fetch 操作中的所有请求均已成功。
backgroundfetchfail 一次 background fetch 操作中至少有一个请求失败。
backgroundfetchabort 用户或应用取消了该 background fetch 操作。
backgroundfetchclick 用户点击了浏览器为该 background fetch 操作显示的界面。

backgroundfetchsuccess 与 backgroundfetchfail 是 BackgroundFetchUpdateUIEvent 实例;backgroundfetchabort 与 backgroundfetchclick 是 BackgroundFetchEvent 实例。这四个事件并非进度事件——下载进度是通过 BackgroundFetchRegistration 上的 progress 事件单独报告的,该事件会在 uploaded、downloaded、result 或 failureReason 发生变化时触发。

  • 调用 fetch() 前先用 "BackgroundFetchManager" in self 做特性检测,并在不支持时走真实 fallback 分支(而非仅留注释)。
  • 将属于同一整体的文件(如媒体文件及其封面图)合并到一次 fetch() 调用中,使其作为单个用户可见的下载呈现。
  • 将 downloadTotal 设置为下载的总字节数;BackgroundFetchRegistration.failureReason 的可能取值中包含 "download-total-exceeded",其他取值还有 "aborted"、"bad-status"、"fetch-error" 与 "quota-exceeded"。
  • 在 service worker 中处理 backgroundfetchsuccess 与 backgroundfetchfail,以便下载完成后进行处理或清理。
  • 处理 backgroundfetchclick,在用户点击浏览器的下载通知时将其带到相关界面。
  • 不要将 Background Fetch 用于不需要用户可见进度的短任务——它专为大型、长时间运行的下载而设计。