חדש: אחזור רקע

ג'ייק ארצ'יבלד
ג'ייק ארצ'יבלד

בשנת 2015 השקנו את התכונה 'סנכרון ברקע', שמאפשרת ל-Service Worker לדחות את העבודה עד שלמשתמש יש קישוריות. המשמעות היא שהמשתמש יכול להקליד הודעה, ללחוץ על 'שלח' ולצאת מהאתר בידיעה שההודעה תישלח עכשיו או כשתהיה לו קישוריות.

זו תכונה שימושית, אבל מחייב שה-Service Worker יהיה פעיל במהלך השליפה. זו לא בעיה לחלקים קצרים של עבודה כמו שליחת הודעה, אבל אם המשימה נמשכת יותר מדי זמן, הדפדפן עלול להרוס את קובץ השירות (service worker), אחרת השימוש בו מהווה סיכון לפרטיות המשתמש ולסוללה שלו.

אז מה קורה אם צריך להוריד משהו שעשוי לקחת זמן רב, כמו סרט, פודקאסטים או שלבים של משחק. לשם כך מיועד אחזור רקע.

אחזור ברקע זמין כברירת מחדל החל מגרסה 74 של Chrome.

הנה הדגמה מהירה של שתי דקות שמראה את המצב המסורתי של הדברים, לעומת שימוש באחזור ברקע:

נסו את ההדגמה בעצמכם ועיינו בקוד.

איך זה עובד

אחזור ברקע פועל כך:

  1. אתה מנחה את הדפדפן לבצע קבוצה של אחזורים ברקע.
  2. הדפדפן מאחזר את אותם דברים, ומציג למשתמש את ההתקדמות.
  3. אחרי שהאחזור הושלם או נכשל, הדפדפן פותח את קובץ השירות (service worker) ומפעיל אירוע כדי לומר לך מה קרה. זה המקום שבו אתם מחליטים מה לעשות עם התשובות, אם בכלל.

אם המשתמש סוגר דפים לאתר אחרי שלב 1, זה בסדר, ההורדה תמשיך. מכיוון שהאחזור גלוי מאוד וניתן לבטל אותו בקלות, אין חשש לפרטיות ממשימה של סנכרון ברקע שארוכת מדי. מכיוון שה-Service Worker לא פועל כל הזמן, אין חשש שהוא עלול לנצל לרעה את המערכת, כמו כריית ביטקוין ברקע.

בפלטפורמות מסוימות (כמו Android) הדפדפן יכול להיסגר אחרי שלב 1, כי הדפדפן יכול להעביר את האחזור למערכת ההפעלה.

אם המשתמש מתחיל את ההורדה במצב אופליין או עובר למצב אופליין במהלך ההורדה, השליפה ברקע מושהית ותמשיך מאוחר יותר.

ממשק ה-API

זיהוי תכונה

בדומה לכל תכונה חדשה, עליך לזהות אם הדפדפן תומך בה. לצורך אחזור רקע, זה פשוט כמו:

if ('BackgroundFetchManager' in self) {
  // This browser supports Background Fetch!
}

התחלת אחזור ברקע

ה-API הראשי מנתק רישום של service worker, לכן חשוב לוודא תחילה שרשמתם את Service Worker. לאחר מכן:

navigator.serviceWorker.ready.then(async (swReg) => {
  const bgFetch = await swReg.backgroundFetch.fetch('my-fetch', ['/ep-5.mp3', 'ep-5-artwork.jpg'], {
    title: 'Episode 5: Interesting things.',
    icons: [{
      sizes: '300x300',
      src: '/ep-5-icon.png',
      type: 'image/png',
    }],
    downloadTotal: 60 * 1024 * 1024,
  });
});

הפונקציה backgroundFetch.fetch מתייחסת לשלושה ארגומנטים:

פרמטרים
id string
זיהוי ייחודי של אחזור הרקע הזה.

backgroundFetch.fetch ידחה אם המזהה תואם לאחזור קיים ברקע.

requests Array<Request|string>
הדברים שצריך לאחזר. המערכת תתייחס למחרוזות כאל כתובות URL, והן יהפכו למחרוזות Request דרך new Request(theString).

אפשר לאחזר דברים ממקורות אחרים כל עוד המשאבים מאפשרים זאת דרך CORS.

הערה: בשלב זה, Chrome לא תומך בבקשות שמחייבות קדם-הפעלה של CORS.

options אובייקט שיכול לכלול את הרכיבים הבאים:
options.title string
כותרת לדפדפן שתוצג יחד עם ההתקדמות.
options.icons Array<IconDefinition>
מערך של אובייקטים עם 'src', 'size' ו-'type'.
options.downloadTotal number
הגודל הכולל של גופי התגובה (לאחר ביטול ה-gzip).

למרות שלא חובה לספק נתונים כאלה, מומלץ מאוד. הנתונים משמשים כדי לציין למשתמש את גודל ההורדה ולספק מידע על ההתקדמות. אם לא תספק זאת, הדפדפן יעדכן את המשתמש שהגודל לא ידוע, וכתוצאה מכך יש סיכוי גבוה יותר שהמשתמש יבטל את ההורדה.

אם הורדות האחזור ברקע חורגות מהמספר שמופיע כאן, הן יבוטלו. אין בעיה אם ההורדה קטנה מ-downloadTotal, כך שאם לא ידוע לך מה יהיה הסכום הכולל של ההורדה, עדיף להיזהר.

הפונקציה backgroundFetch.fetch מחזירה הבטחה שנפתרת עם BackgroundFetchRegistration. אני אוסיף את הפרטים מאוחר יותר. ההבטחה דוחה אם המשתמש ביטל את הסכמתו להורדות, או אם אחד מהפרמטרים שסופקו לא חוקי.

כשמספקים הרבה בקשות לאחזור ברקע אחד, אפשר לשלב בין כמה דברים שהמשתמש הגיוני מבחינתו. לדוגמה, סרט יכול להיות מפוצל לאלף מקורות מידע (טיפוסי ל-MPEG-DASH) ולצרף משאבים נוספים, כמו תמונות. אפשר לפזר רמה במשחק על פני משאבים רבים של JavaScript, תמונה ואודיו. אבל מבחינת המשתמש זה רק "הסרט", או "הרמה".

המערכת מאחזרת אחזור קיים ברקע

תוכלו לקבל אחזור קיים ברקע כך:

navigator.serviceWorker.ready.then(async (swReg) => {
  const bgFetch = await swReg.backgroundFetch.get('my-fetch');
});

...על ידי העברת ה-id של אחזור הרקע הרצוי. get מחזירה undefined אם אין אחזור פעיל ברקע עם המזהה הזה.

אחזור ברקע נחשב ל'פעיל' מרגע הרישום שלו, עד שהצלחתו, נכשלה או בוטלה.

אפשר לקבל רשימה של כל אחזורי הרקע הפעילים באמצעות getIds:

navigator.serviceWorker.ready.then(async (swReg) => {
  const ids = await swReg.backgroundFetch.getIds();
});

הרשמות אחזור ברקע

BackgroundFetchRegistration (bgFetch בדוגמאות שלמעלה) כולל את הפרטים הבאים:

תכונות
id string
המזהה של האחזור ברקע.
uploadTotal number
מספר הבייטים שיישלחו לשרת.
uploaded number
מספר הבייטים שנשלחו בהצלחה.
downloadTotal number
הערך שצוין כשמתבצע רישום של האחזור ברקע, או אפס.
downloaded number
מספר הבייטים שהתקבלו בהצלחה.

הערך הזה עשוי לרדת. לדוגמה, אם החיבור מתנתק ואי אפשר להמשיך את ההורדה, הדפדפן יפעיל מחדש את האחזור של המשאב.

result

אחד מהבאים:

  • "" - האחזור ברקע פעיל, ולכן עדיין אין תוצאה.
  • "success" - אחזור ברקע בוצע בהצלחה.
  • "failure" - אחזור ברקע נכשל. הערך הזה מופיע רק כשהאחזור ברקע נכשל לחלוטין, כי הדפדפן לא יכול לנסות שוב/להמשיך.
failureReason

אחד מהבאים:

  • "" – אחזור הרקע לא נכשל.
  • "aborted" – אחזור ברקע בוטל על ידי המשתמש, או בוצעה קריאה ל-abort().
  • "bad-status" – הסטטוס של אחת מהתשובות היה 'לא תקין', למשל 404.
  • "fetch-error" – אחד מהאחזורים נכשל מסיבה אחרת. למשל, CORS , MIX, תגובה חלקית לא חוקית או כשל כללי ברשת לאחזור שלא ניתן לנסות שוב.
  • "quota-exceeded" - מכסת האחסון מוצתה במהלך האחזור ברקע.
  • "download-total-exceeded" - הייתה חריגה מחבילת 'downloadTotal' שצוינה.
recordsAvailable boolean
האם אפשר לגשת לבקשות/תגובות הבסיסיות?

אם השדה מוגדר כ-False, לא ניתן יהיה להשתמש ב-match וב-matchAll.

שיטות
abort() מחזירה Promise<boolean>
מבטלים את האחזור ברקע.

ההבטחה שהוחזרה מסתיימת ב-True אם האחזור בוטל בהצלחה.

matchAll(request, opts) הפונקציה מחזירה את הערך Promise<Array<BackgroundFetchRecord>>
קבלת הבקשות והתגובות.

הארגומנטים כאן זהים ל-cache API. קריאה ללא ארגומנטים מחזירה הבטחה לכל הרשומות.

המידע על הסיבות האפשריות מפורט כאן.

match(request, opts) הפונקציה מחזירה את הערך Promise<BackgroundFetchRecord>
כמפורט למעלה, אבל נפתרת בהתאמה הראשונה.
אירועים
progress הופעל כשאחד מהערכים uploaded, downloaded, result או failureReason השתנה.

ההתקדמות במעקב

ניתן לעשות זאת דרך האירוע progress. חשוב לזכור ש-downloadTotal הוא הערך שסיפקתם, או 0 אם לא ציינתם ערך.

bgFetch.addEventListener('progress', () => {
  // If we didn't provide a total, we can't provide a %.
  if (!bgFetch.downloadTotal) return;

  const percent = Math.round(bgFetch.downloaded / bgFetch.downloadTotal * 100);
  console.log(`Download progress: ${percent}%`);
});

קבלת הבקשות והתגובות

bgFetch.match('/ep-5.mp3').then(async (record) => {
  if (!record) {
    console.log('No record found');
    return;
  }

  console.log(`Here's the request`, record.request);
  const response = await record.responseReady;
  console.log(`And here's the response`, response);
});

record הוא BackgroundFetchRecord, והוא נראה כך:

תכונות
request Request
הבקשה שנמסרה.
responseReady Promise<Response>
התגובה שאוחזרה.

התשובה נמצאת מאחורי הבטחה כי ייתכן שהיא עדיין לא התקבלה. ההבטחה תידחה אם האחזור ייכשל.

אירועים של קובץ שירות (service worker)

אירועים
backgroundfetchsuccess הכל אוחזר בהצלחה.
backgroundfetchfailure אחזור אחד או יותר נכשל.
backgroundfetchabort אחזור אחד או יותר נכשלו.

אפשרות זו שימושית רק אם ברצונך לנקות נתונים קשורים.

backgroundfetchclick המשתמש לחץ על ממשק המשתמש של התקדמות ההורדה.

לאובייקטים של האירוע יש את הפריטים הבאים:

תכונות
registration BackgroundFetchRegistration
שיטות
updateUI({ title, icons }) מאפשרת לשנות את הכותרת או הסמלים שהגדרתם בהתחלה. לא חובה לעשות זאת, אבל אפשר לתת הקשר נוסף במקרה הצורך. ניתן לעשות זאת *פעם אחת* רק במהלך אירועים של backgroundfetchsuccess ו-backgroundfetchfailure.

תגובה לכישלון/הצלחה

כבר ראינו את האירוע progress, אבל זה שימושי רק כשיש למשתמש דף פתוח באתר. היתרון העיקרי של אחזור ברקע הוא שממשיכים לעבוד אחרי שהמשתמש עוזב את הדף, או אפילו סוגר את הדפדפן.

אם אחזור ברקע הושלם בהצלחה, קובץ השירות (service worker) יקבל את האירוע backgroundfetchsuccess ו-event.registration יהיה הרישום לאחזור ברקע.

אחרי האירוע הזה לא תהיה יותר גישה לבקשות ולתגובות שאוחזרו, לכן אם תרצו לשמור אותן, תצטרכו להעביר אותן למיקום כמו cache API.

כמו ברוב האירועים של Service Worker, משתמשים ב-event.waitUntil כדי שה-Service Worker יידע מתי האירוע הושלם.

לדוגמה, בקובץ השירות (service worker):

addEventListener('backgroundfetchsuccess', (event) => {
  const bgFetch = event.registration;

  event.waitUntil(async function() {
    // Create/open a cache.
    const cache = await caches.open('downloads');
    // Get all the records.
    const records = await bgFetch.matchAll();
    // Copy each request/response across.
    const promises = records.map(async (record) => {
      const response = await record.responseReady;
      await cache.put(record.request, response);
    });

    // Wait for the copying to complete.
    await Promise.all(promises);

    // Update the progress notification.
    event.updateUI({ title: 'Episode 5 ready to listen!' });
  }());
});

יכול להיות שהכשל הגיע לשגיאה 404 אחת, שאולי לא הייתה חשובה לכם, לכן אולי כדאי להעתיק חלק מהתגובות למטמון כפי שצוין למעלה.

תגובה ללחיצה

ממשק המשתמש שמציג את התקדמות ההורדה ואת התוצאה ניתנים ללחיצה. תוכלו להגיב על האירוע backgroundfetchclick ב-Service Worker. כפי שמצוין למעלה, event.registration יהיה הרישום של אחזור הנתונים ברקע.

הפעולות הנפוצות שאפשר לבצע באירוע הזה הן לפתוח חלון:

addEventListener('backgroundfetchclick', (event) => {
  const bgFetch = event.registration;

  if (bgFetch.result === 'success') {
    clients.openWindow('/latest-podcasts');
  } else {
    clients.openWindow('/download-progress');
  }
});

משאבים נוספים

תיקון: גרסה קודמת של המאמר הזה התייחסה בטעות ל'אחזור ברקע' כ'תקן אינטרנט'. ה-API לא נמצא כרגע במסלול תקני. ניתן למצוא את המפרט ב-WICG כטיוטת דוח של קבוצת קהילות.