איך להשהות ולשלוח כמה אירועי ניתוח נתונים באצווה

תכונות באינטרנט: fetchLater AbortController ו-AbortSignal

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

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

איך מטמיעים

  1. תזמון הבקשה: ברגע שנתוני ניתוח רלוונטיים זמינים, קוראים ל-fetchLater() עם המטען הייעודי (payload) של הנתונים ומעבירים ערך activateAfter. הפעולה הזו מוסיפה את הנתונים לתור כדי שיישלחו אחרי פרק הזמן הזה, או אם המשתמש עוזב את הדף לפני כן.

  2. שליחת כמה אירועים ביחד: אם אירוע חדש ב-Analytics מתרחש לפני שפג הזמן הקצוב של activateAfter, מבטלים את הבקשה שתוזמנה קודם וקוראים שוב ל-fetchLater() (עם אותו ערך של activateAfter) עם כל תור האירועים במטען ייעודי (payload) אחד.

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

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

קוד לדוגמה

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

// Replace with your analytics endpoint.
const ANALYTICS_ENDPOINT = '/path/to/analytics/endpoint';

// Replace with a time window of your choice. All analytics events that
// occur within this time window will be batched together.
const BATCH_WINDOW = 10 * 1000;

// The maximum number of events to batch. Pick a number that is unlikely
// to overflow the fetchLater() quota for the page.
const MAX_QUEUE_SIZE = 100;

const eventQueue = [];
let fetchLaterResult;
let fetchLaterController;

function trackEvent(eventData) {
  // If the previously queued beacon has already been sent, or if the
  // max queue size has been met, reset the queue.
  if (fetchLaterResult?.activated || eventQueue.length > MAX_QUEUE_SIZE) {
    fetchLaterController = null;
    fetchLaterResult = null;
    eventQueue.length = 0;
  }

  eventQueue.push(eventData);

  // Abort any pending beacons before creating a new one.
  if (fetchLaterController) {
    fetchLaterController.abort();
  }
  fetchLaterController = new AbortController();

  // Schedule a fetch for the events to be sent when the batch window expires.
  // IMPORTANT: wrap the call in a try/catch to handle quota errors.
  try {
    fetchLaterResult = fetchLater(ANALYTICS_ENDPOINT, {
      method: 'POST',
      headers: {'content-type': 'application/json'},
      body: JSON.stringify(eventQueue),
      signal: fetchLaterController.signal,
      activateAfter: BATCH_WINDOW,
    });
  } catch (error) {
    // Handle errors as needed.
  }
}

// Track page loads.
window.addEventListener('load', () => {
  trackEvent({type: 'page_load'});
});

// Track click events.
window.addEventListener('click', (event) => {
  trackEvent({type: 'click', target: serializeElement(event.target)});
});

שיטות מומלצות

  • מומלץ להשתמש ב-fetchLater() עם האפשרות activateAfter כדי לאסוף כמה אירועי ניתוח נתונים שמתרחשים בסמיכות זה לזה.
  • מומלץ להשתמש ב-AbortController כדי לבטל את השליפה בהמתנה במקרים שבהם מתרחש אירוע ניתוח נתונים חדש.
  • חשוב לוודא שלא מקבצים יותר מדי אירועים יחד וחורגים מהמכסה של fetchLater() (כרגע בערך 64KB לכל מקור).
  • מומלץ לעטוף קריאות ל-fetchLater() ב-try/catch כדי לטפל בשגיאות שקשורות למכסות.
  • מומלץ להשתמש בזיהוי תכונות כדי לבדוק אם fetchLater() קיים ב-globalThis, ולהטמיע אסטרטגיית חלופות לדפדפנים שלא תומכים ב-API.
  • אל תשתמשו באובייקט ReadableStream בגוף הבקשה, כי זה יגרום לשגיאה.

תמיכה בדפדפנים ושיטות חלופיות

לכן, בדרך כלל נדרשת אסטרטגיית גיבוי.

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

‫fetchLater() polyfill

משתמשים ב-polyfill המינימלי הבא של fetchLater(), שמטמיע את ה-API בצורה הכי קרובה שאפשר בדפדפנים שלא תומכים בו.

ההבדל היחיד בהתנהגות של ה-polyfill הזה הוא שבמקום לשלוח את המטען הייעודי (payload) כשהמשתמש עוזב את הדף, הוא שולח אותו בכל פעם שה-visibilityState של הדף משתנה ל-"hidden", כי זה האות הכי אמין לסיום הסשן שזמין כיום באופן נרחב.

globalThis.fetchLater ??= function fetchLater(url, init = {}) {
  let timeoutHandle;
  let activated = false;

  function sendNow() {
    if (!(init.signal && init.signal.aborted)) {
      // Use fetch keepalive if the browser supports it or if custom fetch
      // parameters are specified (e.g. custom headers or methods).
      // Otherwise fall back to `navigator.sendBeacon()`.
      if (
        'keepalive' in Request.prototype ||
        init.method !== 'POST' ||
        init.headers
      ) {
        fetch(url, Object.assign({}, init, {keepalive: true}));
        activated = true;
      } else {
        activated = navigator.sendBeacon(url, init.body);
      }
    }
    destroy();
  }

  function destroy() {
    document.removeEventListener('visibilitychange', sendNow);
    clearTimeout(timeoutHandle);
  }

  if (document.visibilityState === 'hidden') {
    // If the beacon was created while the page is already hidden, send data
    // ASAP but wait until the next microtask to allow all sync code to run.
    queueMicrotask(sendNow);
  } else {
    document.addEventListener('visibilitychange', sendNow);

    if (typeof init.activateAfter === 'number' && init.activateAfter >= 0) {
      timeoutHandle = setTimeout(sendNow, init.activateAfter);
    }
  }

  if (init.signal) {
    init.signal.addEventListener('abort', destroy);
  }

  return {
    get activated() {
      return activated;
    },
  };
};