פורסם: 20 בפברואר 2025
הורדה מהימנה של מודלים גדולים של AI היא משימה מאתגרת. אם החיבור לאינטרנט של המשתמשים מתנתק או שהם סוגרים את האתר או את אפליקציית האינטרנט שלכם, הם מאבדים את קובצי המודל שהורדו באופן חלקי וצריכים להתחיל מחדש כשהם חוזרים לדף שלכם. שימוש ב-Background Fetch API כשיפור מתקדם יכול לשפר משמעותית את חוויית המשתמש.
רישום קובץ שירות (service worker)
כדי להשתמש ב-Background Fetch API, האפליקציה צריכה לרשום Service Worker.
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}`);
});
הודעה למשתמשים וללקוח על השלמת האחזור
כשהאחזור ברקע מצליח, ה-service worker של האפליקציה מקבל אירוע backgroundfetchsuccess.
הקוד הבא כלול ב-service worker. הקריאה
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. אחרי שמקבלים את ההודעה מ-service worker, אפשר לעבוד עם מודל ה-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);
}
});
ביטול אחזור נתונים ברקע
כדי לאפשר למשתמש לבטל הורדה שמתבצעת, משתמשים בשיטה abort() של BackgroundFetchRegistration.
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 טובה יותר בצד הלקוח עבור המשתמשים שלכם.
הדגמה (דמו)
אפשר לראות הטמעה מלאה של הגישה הזו בהדגמה ובקוד המקור שלה.
תודות
המדריך הזה נבדק על ידי François Beaufort, Andre Bandarra, Sebastian Benz, Maud Nalpas ועל ידי Alexandra Klepper.