Birden fazla analiz etkinliğini birleştirme ve toplu işlem yapma

Çoğu analiz, telemetri ve Core Web Vitals analiz verisi, kullanıcıya yanıt vermekten daha düşük önceliklidir. Bu nedenle, bu verileri göndermeyi kullanıcı sayfadan ayrılana kadar güvenle erteleyebilirsiniz. Anlık güncellemeler sunmanız gereken durumlar bu kuralın istisnasıdır. Bu durumda zamanlama önemlidir.

İşaretçileri en aza indirirken gerçek zamanlı analiz güncellemeleri sağlamanın en iyi yolu, activateAfter yapılandırma seçeneğiyle fetchLater() kullanmaktır. Bu sayede, belirli bir zaman aralığında gerçekleşen tüm analiz etkinliklerini tek bir işaretçide etkili bir şekilde birleştirebilir ve toplu olarak işleyebilirsiniz. Bu işaretçi, kullanıcı zaman aşımı süresi dolmadan sayfadan ayrılsa bile güvenilir bir şekilde gönderilir.

Nasıl uygulanır?

  1. İsteği planlayın: Alakalı analiz verileri kullanılabilir hale gelir gelmez veri yükünüzle fetchLater() yöntemini çağırın ve bir activateAfter değeri iletin. Bu işlem, verileri bu süre geçtikten sonra veya kullanıcı sayfadan daha önce ayrılırsa gönderilmek üzere sıraya alır.

  2. Birden fazla etkinliği toplu olarak işleme: activateAfter zaman aşımı süresi dolmadan önce yeni bir analiz etkinliği gerçekleşirse önceden planlanan isteği iptal edin ve tek bir yükte tam etkinlik sırasıyla fetchLater() işlevini tekrar (aynı activateAfter değeriyle) çağırın.

  3. Zaman aşımı süresi dolduğunda etkinlik sırasını sıfırla: Planlanan işaretçi başarıyla gönderildikten sonra yeni bir analiz etkinliği gerçekleşirse (ör. fetchLater() sonucunun activated değeri true ise) etkinlik sırasını sıfırlayın.

  4. Gerisini tarayıcıya bırakın: Kullanıcı, activateAfter zaman aşımı süresi dolmadan önce sayfadan ayrılır veya sekmeyi kapatırsa tarayıcı, en son fetchLater() çağrınızdaki yükü güvenilir bir şekilde göndermeye devam eder.

Örnek kod

Bu kod, bir sayfadaki tüm load ve click etkinliklerini izler ve 10 saniyelik zaman aşımı süresi içinde gerçekleşen tüm etkinlikleri gruplandırır.

// 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)});
});

En İyi Uygulamalar

  • Birbirine yakın zamanlarda gerçekleşen birden fazla analiz etkinliğini toplu olarak işlemek için fetchLater() ile activateAfter seçeneğini KULLANIN.
  • Yeni bir analiz etkinliği gerçekleştiğinde beklemedeki getirme işlemini iptal etmek için AbortController KULLANIN.
  • MUTLAKA çok fazla etkinliği bir araya getirip fetchLater() kotasını (şu anda kaynak başına yaklaşık 64 KB) aşmadığınızdan emin olun.
  • MUTLAKA kota hatalarını işlemek için fetchLater() çağrılarını try/catch içine alın.
  • globalThis üzerinde fetchLater()'nin varlığını tespit etmek için özellik algılamayı KULLANIN ve API'yi desteklemeyen tarayıcılar için bir yedek strateji uygulayın.
  • İstek gövdesi için ReadableStream nesnesi KULLANMAYIN. Aksi takdirde hata alırsınız.

Tarayıcı desteği ve yedek stratejiler

Bu nedenle, genellikle bir yedek strateji gerekir.

Ancak bu API'nin daha yüksek güvenilirlik ve performans avantajları göz önüne alındığında, tarayıcı destekliyorsa fetchLater() kullanılmalıdır.

fetchLater() polyfill

Desteklenmeyen tarayıcılarda API'yi mümkün olduğunca yakın bir şekilde uygulayan aşağıdaki minimal fetchLater() polyfill'i kullanın.

Bu polyfill ile ilgili tek önemli davranış farkı, kullanıcı sayfadan ayrıldığında yükü göndermek yerine, sayfanın visibilityState değeri "gizli" olarak değiştiğinde göndermesidir. Bu, günümüzde yaygın olarak kullanılan en güvenilir oturum sonu sinyalidir.

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;
    },
  };
};