เผยแพร่: 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