公開日: 2025 年 2 月 20 日
大規模な AI モデルを確実にダウンロードすることは、困難なタスクです。ユーザーがインターネット接続を失ったり、ウェブサイトまたはウェブ アプリケーションを閉じたりすると、ダウンロードが完了していないモデルファイルが失われ、ページに戻ったときに最初からやり直す必要があります。Background Fetch API を段階的な拡張として使用することで、ユーザー エクスペリエンスを大幅に改善できます。
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);
});
}
バックグラウンド フェッチをトリガーする
ブラウザがフェッチすると、進行状況がユーザーに表示され、ダウンロードをキャンセルする方法が提示されます。ダウンロードが完了すると、ブラウザはサービス ワーカーを開始し、アプリケーションはレスポンスでアクションを実行できます。
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();
モデルをキャッシュに保存する
ダウンロードしたモデルをキャッシュに保存して、ユーザーがモデルを 1 回だけダウンロードするようにします。Background Fetch API はダウンロード エクスペリエンスを向上させますが、クライアントサイド AI では常に可能な限り小さいモデルを使用するようにしてください。
これらの API を組み合わせることで、ユーザー向けのクライアントサイド AI エクスペリエンスを向上させることができます。
デモ
このアプローチの完全な実装については、デモとそのソースコードをご覧ください。
![Chrome DevTools の [アプリケーション] パネルが開き、バックグラウンド フェッチのダウンロードが表示されている。](https://web-dot-google-developers.gonglchuangl.net/static/articles/background-fetch-ai/background-fetch-ai-demo.png?hl=ja)
謝辞
このガイドは、François Beaufort、Andre Bandarra、Sebastian Benz、Maud Nalpas、Alexandra Klepper がレビューしました。