跳转到内容

Service Worker · API

Background Fetch API

发布于

有限可用不支持的浏览器: Firefox (Desktop)、Firefox (Android)、Safari (macOS)、Safari (iOS)WICG 草案

Background Fetch API 让页面或 service worker 把一组请求作为一个有名字的下载任务交给浏览器,标签页关闭后任务继续进行;浏览器在自己的界面中显示进度与取消控件,并在任务落定时在 service worker 中触发事件。它自 Chrome 74、Edge 79、Samsung Internet 11 起可用,Firefox 与 Safari 均未实现(BCD api.BackgroundFetchManager),因此它只能作为普通 fetch() 下载之上的增强,而不是替代。

// 页面或 service worker 中;registration 来自 navigator.serviceWorker.ready
const bgFetch = await registration.backgroundFetch.fetch(id, requests, options);
const existing = await registration.backgroundFetch.get(id); // 没有该 id 的任务时为 undefined
const ids = await registration.backgroundFetch.getIds(); // string[]
// service worker 中
self.addEventListener('backgroundfetchsuccess', (event) => { /* event.registration */ });
self.addEventListener('backgroundfetchfail', (event) => { /* event.registration.failureReason */ });
self.addEventListener('backgroundfetchabort', (event) => { /* 用户或应用取消 */ });
self.addEventListener('backgroundfetchclick', (event) => { /* 用户点击了浏览器界面 */ });

fetch() 在浏览器接受任务后立即返回 Promise<BackgroundFetchRegistration>,远早于任何字节到达。任务落定时,backgroundfetchsuccess、backgroundfetchfail、backgroundfetchabort 三者恰好触发一个;backgroundfetchclick 只在用户点击浏览器的下载界面时触发。前两个是 BackgroundFetchUpdateUIEvent 实例,其 updateUI({ title, icons }) 可修改完成后的通知;后两个是普通的 BackgroundFetchEvent 实例。

fetch() 接受两个必填参数和一个选项对象。

名称 类型 说明
id string 开发者自定的标识符,之后传给 get(id)。每个 id 同时只能有一个进行中的注册;第一个任务未完成时复用同一 id 会被拒绝。
requests RequestInfo | RequestInfo[] 一个 Request 或 URL 字符串,或它们的数组。所有请求作为一个用户可见的任务一起下载。mode: 'no-cors' 的请求会被拒绝。
options.title string 浏览器进度界面中显示的标题。
options.icons ImageResource[] 含 src 以及可选的 sizes、type、label 的对象,用于进度界面。
options.downloadTotal number 预估总大小(字节)。它驱动进度条,同时也是硬上限:已下载字节一旦超过它,浏览器就会中止任务,并把 failureReason 置为 "download-total-exceeded"。

返回的 BackgroundFetchRegistration 暴露 id、downloadTotal、downloaded、uploadTotal、uploaded、result(""、"success" 或 "failure")、failureReason(""、"aborted"、"bad-status"、"fetch-error"、"quota-exceeded" 或 "download-total-exceeded")、recordsAvailable、abort()、match()、matchAll(),以及一个在任一计数器或 result 变化时触发的 progress 事件。

fetch() 在以下情况拒绝其 Promise(MDN,BackgroundFetchManager.fetch())。

  • TypeError:没有给出任何请求、某个请求使用了 mode: 'no-cors'、注册上没有 service worker、同一 id 的注册已存在,或请求对象无法创建。
  • AbortError(DOMException):任务在被接受之前就被中止。
  • NotAllowedError(DOMException):用户代理未授予该源发起后台下载的权限;规范把这项权限检查留给浏览器,因此具体条件由浏览器定义。
  • QuotaExceededError:存储请求时超出了该源的存储配额。响应在 service worker 读取之前保存在与 Cache Storage 同类的存储中,因此与 caches 计入同一份配额。

下载失败不会让任何 Promise 被拒绝:它触发 backgroundfetchfail,并设置 event.registration.failureReason。"bad-status" 表示某个响应的状态码不是 ok;"fetch-error" 表示网络请求本身在重试后仍然失败。

两个示例都假定已在 /sw.js 注册了 service worker,且页面处于安全源。

把一集音频与封面图作为一个任务下载

Section titled “把一集音频与封面图作为一个任务下载”

页面以同一个 id 请求音频文件及其封面,浏览器因此只显示一条进度通知;downloadTotal 是此前从服务端 API 取得的两个 Content-Length 之和。

async function downloadEpisode(episode) {
const registration = await navigator.serviceWorker.ready;
const bgFetch = await registration.backgroundFetch.fetch(
`episode-${episode.id}`,
[episode.audioUrl, episode.artworkUrl],
{
title: episode.title,
icons: [{ src: '/icons/download-192.png', sizes: '192x192', type: 'image/png', label: 'Episode download' }],
downloadTotal: episode.audioBytes + episode.artworkBytes,
},
);
bgFetch.addEventListener('progress', () => {
const percent = Math.round((bgFetch.downloaded / bgFetch.downloadTotal) * 100);
document.querySelector('#progress').value = percent;
});
}

progress 监听器只在这个页面打开期间运行;标签页关闭后,浏览器自己的界面就是唯一的进度指示,这正是该 API 的意义所在。

service worker 在 backgroundfetchsuccess 中把每个响应复制到一个命名缓存,然后修改通知文字;失败时记录原因并保持通知原样,让用户看到浏览器的失败状态。

self.addEventListener('backgroundfetchsuccess', (event) => {
event.waitUntil((async () => {
const cache = await caches.open('episodes');
const records = await event.registration.matchAll();
await Promise.all(records.map(async (record) => {
const response = await record.responseReady;
await cache.put(record.request, response);
}));
await event.updateUI({ title: `${event.registration.id} is ready to play` });
})());
});
self.addEventListener('backgroundfetchfail', (event) => {
console.warn('background fetch failed:', event.registration.failureReason);
});
self.addEventListener('backgroundfetchclick', () => {
clients.openWindow('/downloads/');
});

record.responseReady 是一个 Promise,因为部分失败的任务触发事件时,某条记录的响应可能仍在传输;在 waitUntil() 内 await 它可以让 worker 存活到复制完成。

Firefox 与 Safari 的注册对象上没有 backgroundFetch 属性。回退方案执行普通 fetch(),并通过临时的 <a download> 触发浏览器的保存对话框;它只在页面保持打开时有效,界面应明确告知这一点。

async function downloadWithFallback(id, urls, title) {
const registration = await navigator.serviceWorker.ready;
if ('backgroundFetch' in registration) {
return registration.backgroundFetch.fetch(id, urls, { title });
}
for (const url of urls) {
const response = await fetch(url);
if (!response.ok) throw new Error(`${url}: ${response.status}`);
const link = document.createElement('a');
link.href = URL.createObjectURL(await response.blob());
link.download = new URL(url, location.href).pathname.split('/').pop();
link.click();
URL.revokeObjectURL(link.href);
}
}

前台路径会在保存前把整个文件缓冲在内存中,因此超大文件应逐个下载,并提醒用户不要离开页面。

规范

规范状态
Background Fetch(后台获取)WICG 草案
  • 图例
  • 支持
  • 部分支持
  • 需开启标志
  • 不支持
  • 未知
浏览器 / 平台支持版本置信度来源备注
Chrome (Desktop)支持74高来源—
Chrome (Android)支持74高来源1
Edge (Desktop)支持79高来源2
Firefox (Desktop)不支持—高来源3
Firefox (Android)不支持—高来源45
Safari (macOS)不支持—高来源6
Safari (iOS)不支持—高来源78
Samsung Internet支持11.0高来源9
WebView (Android)不支持—高来源10
  1. 由 browser-compat-data 镜像自 Chrome 的数据推导。
  2. 由 browser-compat-data 镜像自 Chrome 的数据推导。
  3. browser-compat-data 未记录 Firefox 的支持。
  4. browser-compat-data 未记录 Firefox for Android 的支持。
  5. 由 browser-compat-data 镜像自 Firefox 的数据推导。
  6. browser-compat-data 未记录 Safari 的支持。
  7. browser-compat-data 未记录 iOS 版 Safari 的支持。
  8. 由 browser-compat-data 镜像自 Safari 的数据推导。
  9. 由 browser-compat-data 镜像自 Chrome Android 的数据推导。
  10. browser-compat-data 未记录 WebView Android 的支持。

源数据: /compatibility/background-fetch.json · 全球使用占比: 72 % (StatCounter 2026-05)

来源: 规范 · MDN · 最近核验 2026-10-03 · 置信度: 高 (由来源计算)