發布日期:2025 年 2 月 20 日
穩定下載大型 AI 模型是一項艱鉅的任務。如果使用者無法連上網際網路或關閉網站或網頁應用程式,就會失去部分下載的模型檔案,並必須在返回網頁時重新開始。使用 Background Fetch API 做為漸進式增強功能,可大幅改善使用者體驗。
註冊 Service Worker
背景擷取 API 要求應用程式註冊服務工作者。
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
const registration = await navigator.serviceWorker.register('sw.js');
console.log('Service worker registered for scope', registration.scope);
});
}
觸發背景擷取
瀏覽器擷取內容時,會向使用者顯示進度,並提供取消下載的方法。下載完成後,瀏覽器會啟動服務工作者,應用程式就能根據回應採取行動。
Background Fetch API 甚至可以準備擷取作業,以便在離線時開始執行。使用者重新連線後,系統就會開始下載。如果使用者離線,系統會暫停處理程序,直到使用者重新上線為止。
在以下範例中,使用者點選按鈕下載 Gemma 2B。在擷取前,我們會檢查模型是否先前已下載並快取,以免使用不必要的資源。如果未快取,我們會啟動背景擷取作業。
const FETCH_ID = 'gemma-2b';
const MODEL_URL =
'https://storage.googleapis.com/jmstore/kaggleweb/grader/g-2b-it-gpu-int4.bin';
downloadButton.addEventListener('click', async (event) => {
// If the model is already downloaded, return it from the cache.
const modelAlreadyDownloaded = await caches.match(MODEL_URL);
if (modelAlreadyDownloaded) {
const modelBlob = await modelAlreadyDownloaded.blob();
// Do something with the model.
console.log(modelBlob);
return;
}
// The model still needs to be downloaded.
// Feature detection and fallback to classic `fetch()`.
if (!('BackgroundFetchManager' in self)) {
try {
const response = await fetch(MODEL_URL);
if (!response.ok || response.status !== 200) {
throw new Error(`Download failed ${MODEL_URL}`);
}
const modelBlob = await response.blob();
// Do something with the model.
console.log(modelBlob);
return;
} catch (err) {
console.error(err);
}
}
// The service worker registration.
const registration = await navigator.serviceWorker.ready;
// Check if there's already a background fetch running for the `FETCH_ID`.
let bgFetch = await registration.backgroundFetch.get(FETCH_ID);
// If not, start a background fetch.
if (!bgFetch) {
bgFetch = await registration.backgroundFetch.fetch(FETCH_ID, MODEL_URL, {
title: 'Gemma 2B model',
icons: [
{
src: 'icon.png',
size: '128x128',
type: 'image/png',
},
],
downloadTotal: await getResourceSize(MODEL_URL),
});
}
});
getResourceSize()
函式會傳回下載檔案的位元組大小。您可以透過提出 HEAD
要求來實作這項功能。
const getResourceSize = async (url) => {
try {
const response = await fetch(url, { method: 'HEAD' });
if (response.ok) {
return response.headers.get('Content-Length');
}
console.error(`HTTP error: ${response.status}`);
return 0;
} catch (error) {
console.error('Error fetching content size:', error);
return 0;
}
};
報表下載進度
背景擷取作業開始後,瀏覽器會傳回 BackgroundFetchRegistration
。您可以使用此方法,搭配 progress
事件,向使用者告知下載進度。
bgFetch.addEventListener('progress', (e) => {
// There's no download progress yet.
if (!bgFetch.downloadTotal) {
return;
}
// Something went wrong.
if (bgFetch.failureReason) {
console.error(bgFetch.failureReason);
}
if (bgFetch.result === 'success') {
return;
}
// Update the user about progress.
console.log(`${bgFetch.downloaded} / ${bgFetch.downloadTotal}`);
});
通知使用者和用戶端擷取完成
背景擷取成功時,應用程式的服務工作站會收到 backgroundfetchsuccess
事件。
服務工作者包含下列程式碼。結尾處的 updateUI()
呼叫可讓您更新瀏覽器介面,通知使用者背景擷取作業已成功。最後,請向用戶端通知下載作業已完成,例如使用 postMessage()
。
self.addEventListener('backgroundfetchsuccess', (event) => {
// Get the background fetch registration.
const bgFetch = event.registration;
event.waitUntil(
(async () => {
// Open a cache named 'downloads'.
const cache = await caches.open('downloads');
// Go over all records in the background fetch registration.
// (In the running example, there's just one record, but this way
// the code is future-proof.)
const records = await bgFetch.matchAll();
// Wait for the response(s) to be ready, then cache it/them.
const promises = records.map(async (record) => {
const response = await record.responseReady;
await cache.put(record.request, response);
});
await Promise.all(promises);
// Update the browser UI.
event.updateUI({ title: 'Model downloaded' });
// Inform the clients that the model was downloaded.
self.clients.matchAll().then((clientList) => {
for (const client of clientList) {
client.postMessage({
message: 'download-complete',
id: bgFetch.id,
});
}
});
})(),
);
});
接收服務工作架構傳送的訊息
如要接收有關用戶端已完成下載的傳送成功訊息,請監聽 message
事件。收到服務工作站傳送的訊息後,您就可以使用 AI 模型,並透過 Cache API 進行儲存。
navigator.serviceWorker.addEventListener('message', async (event) => {
const cache = await caches.open('downloads');
const keys = await cache.keys();
for (const key of keys) {
const modelBlob = await cache
.match(key)
.then((response) => response.blob());
// Do something with the model.
console.log(modelBlob);
}
});
取消背景擷取作業
如要讓使用者取消正在進行的下載作業,請使用 BackgroundFetchRegistration
的 abort()
方法。
const registration = await navigator.serviceWorker.ready;
const bgFetch = await registration.backgroundFetch.get(FETCH_ID);
if (!bgFetch) {
return;
}
await bgFetch.abort();
快取模型
快取已下載的模型,讓使用者只下載一次模型。雖然背景擷取 API 可改善下載體驗,但您應盡量在用戶端 AI 中使用最小型模型。
這些 API 可搭配使用,協助您為使用者打造更優質的用戶端 AI 體驗。
示範

特別銘謝
本指南由 François Beaufort、Andre Bandarra、Sebastian Benz、Maud Nalpas 和 Alexandra Klepper 審查。