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