使用 Background Fetch API 下載 AI 模型

發布日期:2025 年 2 月 20 日

可靠地下載大型 AI 模型是一項艱鉅的任務。如果使用者失去網際網路連線或關閉您的網站/網頁應用程式,部分下載的模型檔案就會遺失,且必須在返回頁面時重新開始下載。使用 Background Fetch API 做為漸進式強化功能,可大幅提升使用者體驗。

Browser Support

  • Chrome: 74.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Source

註冊 Service Worker

Background Fetch 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);
  });
}

觸發背景擷取

瀏覽器擷取資料時,會向使用者顯示進度,並提供取消下載的方法。下載完成後,瀏覽器會啟動 Service Worker,應用程式即可根據回應採取行動。

即使裝置處於離線狀態,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,
          });
        }
      });
    })(),
  );
});

接收來自 Service Worker 的訊息

如要在用戶端接收下載完成的成功訊息,請監聽 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);
  }
});

取消背景擷取作業

如要讓使用者取消進行中的下載作業,請使用 BackgroundFetchRegistrationabort() 方法。

const registration = await navigator.serviceWorker.ready;
const bgFetch = await registration.backgroundFetch.get(FETCH_ID);
if (!bgFetch) {
  return;
}
await bgFetch.abort();

快取模型

快取下載的模型,讓使用者只需下載一次模型。雖然 Background Fetch API 可提升下載體驗,但您應盡可能在用戶端 AI 中使用最小的模型。

這些 API 共同協助您為使用者打造更優質的用戶端 AI 體驗。

示範

如要查看這個方法的完整實作方式,請參閱示範原始碼

Chrome 開發人員工具的「應用程式」面板已開啟,並顯示「背景擷取」下載作業。
您可以使用 Chrome 開發人員工具,預覽與進行中背景擷取作業相關的事件。這個範例顯示正在進行的下載作業,已完成 17.54 MB,總大小為 1.26 GB。瀏覽器的下載指標也會顯示正在進行的下載作業。

特別銘謝

本指南由 François BeaufortAndre BandarraSebastian BenzMaud NalpasAlexandra Klepper 審查。