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

> Background Fetch API 如何把长时间下载任务交给浏览器本身处理——浏览器显示进度与取消控件，并在下载完成后通过事件唤醒 service worker。

**一句话：** 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()`：

```js
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 事件

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）](/zh/reference/service-worker/periodic-background-sync/) —— 另一个用于后台操作的相关 service worker API