Background Fetch:不受标签页关闭影响的大文件下载
发布于
一句话: Background Fetch 提供了一种管理可能耗时较长的下载(如电影、音频文件、软件)的方法——浏览器以用户可见的方式执行这些抓取,显示进度并提供取消方式,下载完成后唤醒 service worker。
它解决的问题
Section titled “它解决的问题”当网页应用要求用户下载大文件时,用户通常需要停留在页面上下载才能完成——关闭标签页或离开页面会中止下载。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 在抓取完成前保持存活,而浏览器为节省电量最终会终止该任务。
注册一次抓取
Section titled “注册一次抓取”先做特性检测,再对 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 事件
Section titled “Service worker 事件”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 用于不需要用户可见进度的短任务——它专为大型、长时间运行的下载而设计。
- 周期性后台同步(Periodic background sync) —— 另一个用于后台操作的相关 service worker API