תיבת עבודה-רקע-סנכרון

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

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

השירות 'סנכרון ברקע של Workbox' נועד להקל על השימוש ב-BackgroundSync API ולשלב את השימוש בו עם מודולים אחרים של Workbox. היא גם מיישמת אסטרטגיית גיבוי לדפדפנים שעדיין לא הטמיעו את BackgroundSync.

דפדפנים שתומכים ב-BackgroundSync API ישיבו בשמכם באופן אוטומטי בקשות שנכשלו במרווח הזמן שמנוהל על ידי הדפדפן, סביר להניח שישתמשו בהשהיה מעריכית לפני ניסיון חוזר (exponential backoff) בין ניסיונות ההפעלה החוזרת. בדפדפנים שלא תומכים במקור ב-BackgroundSync API, הסנכרון ברקע ב-Workbox Background Sync ינסה באופן אוטומטי להפעיל מחדש בכל פעם שה-Service Worker יופעל.

שימוש בסיסי

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

import {BackgroundSyncPlugin} from 'workbox-background-sync';
import {registerRoute} from 'workbox-routing';
import {NetworkOnly} from 'workbox-strategies';

const bgSyncPlugin = new BackgroundSyncPlugin('myQueueName', {
  maxRetentionTime: 24 * 60, // Retry for max of 24 Hours (specified in minutes)
});

registerRoute(
  /\/api\/.*\/*.json/,
  new NetworkOnly({
    plugins: [bgSyncPlugin],
  }),
  'POST'
);

BackgroundSyncPlugin מתחבר אל קריאה חוזרת (callback) של הפלאגין fetchDidFail, ו-fetchDidFail מופעל רק אם מתרחשת שגיאה חריגה, סביר להניח שהסיבה היא כשל ברשת. כלומר, לא יתבצע ניסיון חוזר של בקשות אם מתקבלת תגובה עם סטטוס השגיאה 4xx או 5xx. אם ברצונכם לנסות שוב את כל הבקשות שהתוצאה שלהן היא, למשל סטטוס 5xx, תוכלו לעשות זאת על ידי הוספת פלאגין fetchDidSucceed לאסטרטגיה שלכם:

const statusPlugin = {
  fetchDidSucceed: ({response}) => {
    if (response.status >= 500) {
      // Throwing anything here will trigger fetchDidFail.
      throw new Error('Server error.');
    }
    // If it's not 5xx, use the response as-is.
    return response;
  },
};

// Add statusPlugin to the plugins array in your strategy.

שימוש מתקדם

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

יצירת תור

כדי ליצור תור לסנכרון ברקע של תיבת עבודה, עליך ליצור אותו עם שם תור (שחייב להיות ייחודי למקור שלך):

import {Queue} from 'workbox-background-sync';

const queue = new Queue('myQueueName');

שם התור משמש כחלק משם התג שמקבל את הפרמטר register() מהתג הגלובלי SyncManager. הוא משמש גם כשם Object Store למסד הנתונים IndexedDB.

הוספת בקשה לתור

אחרי שיוצרים את המופע של 'הבאים בתור', אפשר להוסיף אליו בקשות שנכשלו. כדי להוסיף בקשה שנכשלה, יש להפעיל את השיטה .pushRequest(). לדוגמה, הקוד הבא קולט בקשות שנכשלו ומוסיף אותן לתור:

import {Queue} from 'workbox-background-sync';

const queue = new Queue('myQueueName');

self.addEventListener('fetch', event => {
  // Add in your own criteria here to return early if this
  // isn't a request that should use background sync.
  if (event.request.method !== 'POST') {
    return;
  }

  const bgSyncLogic = async () => {
    try {
      const response = await fetch(event.request.clone());
      return response;
    } catch (error) {
      await queue.pushRequest({request: event.request});
      return error;
    }
  };

  event.respondWith(bgSyncLogic());
});

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

בדיקת סנכרון ברקע של תיבת העבודה

למרבה הצער, הבדיקה של BackgroundSync היא לא אינטואיטיבית במידה מסוימת וקשה מכמה סיבות.

הדרך הטובה ביותר לבדוק את ההטמעה היא:

  1. טוענים דף ורושמים את קובץ השירות (service worker).
  2. מכבים את הרשת של המחשב או מכבים את שרת האינטרנט.
    • אין להשתמש בכלי הפיתוח של Chrome במצב אופליין. תיבת הסימון של מצב אופליין בכלי הפיתוח משפיעה רק על בקשות מהדף. בקשות של Service Worker ימשיכו להישלח.
  3. ביצוע בקשות רשת שצריך להוסיף לתור עם Workbox Background Sync.
    • אפשר לבדוק את הבקשות בתור ב-Chrome DevTools > Application > IndexedDB > workbox-background-sync > requests
  4. עכשיו צריך להפעיל את הרשת או את שרת האינטרנט.
  5. כדי לאלץ אירוע sync מוקדם יותר, עוברים אל Chrome DevTools > Application > Service Workers, מזינים את שם התג של workbox-background-sync:<your queue name> כאשר <your queue name> צריך להיות שם התור שהגדרתם, ולוחצים על הלחצן Sync.

    דוגמה ללחצן הסנכרון בכלי הפיתוח ל-Chrome

  6. אתם אמורים לראות שמתבצעות בקשות רשת עבור הבקשות שנכשלו, והנתונים ב-IndexedDB אמורים להיות ריקים כי הבקשות הופעלו מחדש בהצלחה.

סוגים

BackgroundSyncPlugin

מחלקה שמטמיעה את הקריאה החוזרת של מחזור החיים של fetchDidFail. כך קל יותר להוסיף בקשות שנכשלו לתור לסנכרון ברקע.

תכונות

Queue

מחלקה לניהול אחסון הבקשות שנכשלו ב-IndexedDB, וניסיון חוזר מאוחר יותר. ניתן לתעד את כל החלקים בתהליך האחסון וההפעלה מחדש באמצעות קריאה חוזרת (callback).

תכונות

  • Constructor

    void

    יצירת מופע של 'הבאים בתור' עם האפשרויות הנתונות

    הפונקציה constructor נראית כך:

    (name: string,options?: QueueOptions)=> {...}

    • name

      מחרוזת

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

    • אפשרויות

      QueueOptions אופציונלי

  • name

    מחרוזת

  • getAll

    void

    הפונקציה מחזירה את כל הרשומות שתוקפן לא פג (לכל maxRetentionTime). כל הרשומות שהתוקף שלהן פג יוסרו מהתור.

    הפונקציה getAll נראית כך:

    ()=> {...}

    • החזרות

      Promise<QueueEntry[]>

  • popRequest

    void

    מסירה ומחזירה את הבקשה האחרונה בתור (עם חותמת הזמן והמטא-נתונים שלה). האובייקט שמוחזר מופיע בתבנית הבאה: {request, timestamp, metadata}.

    הפונקציה popRequest נראית כך:

    ()=> {...}

    • החזרות

      Promise<QueueEntry>

  • pushRequest

    void

    שמירת הבקשה שהועברה ב-IndexedDB (עם חותמת הזמן והמטא-נתונים שלה) בסוף התור.

    הפונקציה pushRequest נראית כך:

    (entry: QueueEntry)=> {...}

    • רשומה

      QueueEntry

    • החזרות

      Promise<void>

  • registerSync

    void

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

    הפונקציה registerSync נראית כך:

    ()=> {...}

    • החזרות

      Promise<void>

  • replayRequests

    void

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

    הפונקציה replayRequests נראית כך:

    ()=> {...}

    • החזרות

      Promise<void>

  • shiftRequest

    void

    מסירה ומחזירה את הבקשה הראשונה בתור (עם חותמת הזמן והמטא-נתונים שלה). האובייקט שמוחזר מופיע בתבנית הבאה: {request, timestamp, metadata}.

    הפונקציה shiftRequest נראית כך:

    ()=> {...}

    • החזרות

      Promise<QueueEntry>

  • גודל

    void

    מחזירה את מספר הרשומות שנמצאות בתור. חשוב לשים לב שהספירה הזו כוללת גם רשומות שהתוקף שלהן פג (לכל maxRetentionTime).

    הפונקציה size נראית כך:

    ()=> {...}

    • החזרות

      הבטחה<number>

  • unshiftRequest

    void

    שמירת הבקשה שהועברה ב-IndexedDB (עם חותמת הזמן והמטא-נתונים שלה) בתחילת התור.

    הפונקציה unshiftRequest נראית כך:

    (entry: QueueEntry)=> {...}

    • רשומה

      QueueEntry

    • החזרות

      Promise<void>

QueueOptions

תכונות

  • forceSyncFallback

    בוליאני אופציונלי

  • maxRetentionTime

    מספר אופציונלי

  • onSync

    OnSyncCallback אופציונלי

QueueStore

מחלקה לניהול בקשות אחסון מתור ב-IndexedDB, שמתווספת לאינדקס לפי שם התור, לגישה קלה יותר.

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

תכונות

  • Constructor

    void

    משייכת את המכונה הזו למופע של 'הבאים בתור', כך שאפשר לזהות את הרשומות שנוספו לפי שם התור.

    הפונקציה constructor נראית כך:

    (queueName: string)=> {...}

    • queueName

      מחרוזת

  • deleteEntry

    void

    מחיקת הרשומה של המזהה הנתון.

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

    הפונקציה deleteEntry נראית כך:

    (id: number)=> {...}

    • id

      מספר

    • החזרות

      Promise<void>

  • getAll

    void

    מחזירה את כל הרשומות בחנות שתואמות ל-queueName.

    הפונקציה getAll נראית כך:

    ()=> {...}

    • החזרות

      Promise<QueueStoreEntry[]>

  • popEntry

    void

    מסירה ומחזירה את הרשומה האחרונה בתור שתואמת ל-queueName.

    הפונקציה popEntry נראית כך:

    ()=> {...}

    • החזרות

      Promise<QueueStoreEntry>

  • pushEntry

    void

    הוספת ערך אחרון בתור.

    הפונקציה pushEntry נראית כך:

    (entry: UnidentifiedQueueStoreEntry)=> {...}

    • רשומה

      UnidentifiedQueueStoreEntry

    • החזרות

      Promise<void>

  • shiftEntry

    void

    מסירה ומחזירה את הרשומה הראשונה בתור שתואמת ל-queueName.

    הפונקציה shiftEntry נראית כך:

    ()=> {...}

    • החזרות

      Promise<QueueStoreEntry>

  • גודל

    void

    מחזירה את מספר הרשומות בחנות שתואמות ל-queueName.

    הפונקציה size נראית כך:

    ()=> {...}

    • החזרות

      הבטחה<number>

  • unshiftEntry

    void

    צריך להוסיף קודם רשומה בתור.

    הפונקציה unshiftEntry נראית כך:

    (entry: UnidentifiedQueueStoreEntry)=> {...}

    • רשומה

      UnidentifiedQueueStoreEntry

    • החזרות

      Promise<void>

StorableRequest

מחלקה שמקל

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

תכונות

  • Constructor

    void

    מקבל אובייקט של נתוני בקשה שאפשר להשתמש בו כדי ליצור Request, אבל אפשר לשמור אותו גם ב-IndexedDB.

    הפונקציה constructor נראית כך:

    (requestData: RequestData)=> {...}

    • requestData

      RequestData

      אובייקט של נתוני בקשה שכולל את url וגם את כל המאפיינים הרלוונטיים של [requestInit]https://fetch.spec.whatwg.org/#requestinit.

  • שכפול

    void

    יוצרת ומחזירה שכפול עמוק של המכונה.

    הפונקציה clone נראית כך:

    ()=> {...}

  • toObject

    void

    מחזירה שכפול עמוק של האובייקט _requestData של האובייקט.

    הפונקציה toObject נראית כך:

    ()=> {...}

    • החזרות

      RequestData

  • toRequest

    void

    ממיר את המופע הזה לבקשה.

    הפונקציה toRequest נראית כך:

    ()=> {...}

    • החזרות

      בקשה

  • fromRequest

    void

    ממירה אובייקט של Request לאובייקט פשוט שאפשר לשכפל או ליצור מחרוזת JSON.

    הפונקציה fromRequest נראית כך:

    (request: Request)=> {...}

    • בקשה

      בקשה