একাধিক অ্যানালিটিক্স ইভেন্ট ডিবাউন্স ও ব্যাচ করা

ওয়েব ফিচার: fetchLater AbortController এবং AbortSignal

অধিকাংশ অ্যানালিটিক্স, টেলিমেট্রি এবং Core Web Vitals অ্যানালিটিক্স ডেটা ব্যবহারকারীকে উত্তর দেওয়ার চেয়ে কম অগ্রাধিকার পায়, তাই ব্যবহারকারী পৃষ্ঠা ছেড়ে না যাওয়া পর্যন্ত আপনি নিরাপদে এটি পাঠানো পিছিয়ে দিতে পারেন। তবে, আপনাকে রিয়েল-টাইম আপডেট দিতে হলে, সময়মতো তা দেওয়া গুরুত্বপূর্ণ।

রিয়েল-টাইম অ্যানালিটিক্স আপডেট দেওয়ার সবচেয়ে ভালো উপায় হল, activateAfter কনফিগারেশন বিকল্পের সাথে fetchLater() ব্যবহার করা, যাতে বিকন কম ব্যবহার করা যায়। এটি আপনাকে নির্দিষ্ট সময়সীমার মধ্যে ঘটা সমস্ত অ্যানালিটিক্স ইভেন্টকে কার্যকরভাবে ডিবাউন্স এবং ব্যাচ করে একটি একক বিকনে পরিণত করতে দেয়, যা ব্যবহারকারী টাইমআউট শেষ হওয়ার আগে পৃষ্ঠাটি ছেড়ে চলে গেলেও নির্ভরযোগ্যভাবে পাঠানো হয়।

কীভাবে প্রয়োগ করতে হয়

  1. অনুরোধ শিডিউল করুন: প্রাসঙ্গিক কোনও অ্যানালিটিক্স ডেটা উপলভ্য হলেই, আপনার ডেটা পেলোড সহ fetchLater() কল করুন এবং একটি activateAfter ভ্যালু পাস করুন। এটি সেই পরিমাণ সময় অতিবাহিত হওয়ার পরে বা ব্যবহারকারী আগে পৃষ্ঠাটি ছেড়ে চলে গেলে ডেটা পাঠানোর জন্য সারিবদ্ধ করে।

  2. একাধিক ইভেন্ট একসাথে ব্যাচ করুন: activateAfter টাইমআউট শেষ হওয়ার আগে নতুন অ্যানালিটিক্স ইভেন্ট ঘটলে, আগে থেকে শিডিউল করা অনুরোধ বাতিল করুন এবং একটি পেলোডে সম্পূর্ণ ইভেন্ট কিউ সহ আবার fetchLater() কল করুন (একই activateAfter ভ্যালু সহ)।

  3. টাইমআউটের মেয়াদ শেষ হয়ে গেলে ইভেন্ট কিউ রিসেট করুন: শিডিউল করা বিকন সফলভাবে পাঠানোর পরে (অর্থাৎ, fetchLater() ফলাফলের activated ভ্যালু true হলে) নতুন অ্যানালিটিক্স ইভেন্ট ঘটলে, ইভেন্ট কিউ রিসেট করুন।

  4. বাকিটা ব্রাউজারকে ম্যানেজ করতে দিন: activateAfter টাইমআউট শেষ হওয়ার আগে ব্যবহারকারী নেভিগেট করে বেরিয়ে গেলে বা ট্যাব বন্ধ করে দিলে, ব্রাউজার তবুও আপনার সবচেয়ে সাম্প্রতিক fetchLater() কল থেকে পেলোড নির্ভরযোগ্যভাবে পাঠাবে।

কোডের উদাহরণ

এই কোড কোনও পৃষ্ঠার সব load এবং click ইভেন্ট ট্র্যাক করে এবং ১০ সেকেন্ডের টাইমআউটের মধ্যে হওয়া সব ইভেন্ট একসাথে ব্যাচ করে।

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

পেশাদার পদ্ধতি

  • একে অপরের কাছাকাছি ঘটা একাধিক অ্যানালিটিক্স ইভেন্ট ব্যাচ করতে activateAfter বিকল্প সেট করে fetchLater() ব্যবহার করুন।
  • নতুন অ্যানালিটিক্স ইভেন্ট হওয়ার ক্ষেত্রে, বাকি থাকা ফেচ বাতিল করতে AbortController ব্যবহার করুন।
  • আপনি যেন একসাথে অনেক ইভেন্ট ব্যাচ না করেন এবং fetchLater() কোটা (বর্তমানে অরিজিন প্রতি প্রায় ৬৪ কেবি) অতিক্রম না করেন তা নিশ্চিত করতে চেক করুন।
  • কোটা সংক্রান্ত সমস্যা ম্যানেজ করতে fetchLater()-এ করা কল try/catch-এ র‍্যাপ করুন।
  • DO ফিচার globalThis-এ fetchLater()-এর উপস্থিতি শনাক্ত করে এবং যেসব ব্রাউজারে API কাজ করে না সেগুলির জন্য একটি ফলব্যাক স্ট্র্যাটেজি প্রয়োগ করে।
  • অনুরোধের বডির জন্য ReadableStream অবজেক্ট ব্যবহার করবেন না, কারণ এটি করলে সমস্যা হবে।

ব্রাউজার সাপোর্ট ও ফলব্যাক স্ট্র্যাটেজি

তাই, সাধারণত একটি ফলব্যাক স্ট্র্যাটেজি প্রয়োজন হয়।

তবে, এই API-এর উন্নত নির্ভরযোগ্যতা এবং পারফর্ম্যান্সের সুবিধা বিবেচনা করে, ব্রাউজার এটি সাপোর্ট করলে fetchLater() ব্যবহার করা উচিত।

fetchLater() পলিফিল

নিম্নলিখিত ন্যূনতম fetchLater() পলিফিল ব্যবহার করুন, যা কাজ করে না এমন ব্রাউজারে যতটা সম্ভব API প্রয়োগ করে।

এই পলিফিলের সাথে একমাত্র উল্লেখযোগ্য আচরণগত পার্থক্য হল, ব্যবহারকারী পৃষ্ঠাটি ছেড়ে চলে যাওয়ার সময় পেলোড পাঠানোর পরিবর্তে, পৃষ্ঠার visibilityState "লুকানো"তে পরিবর্তিত হলে এটি পাঠায়, কারণ এটিই সবচেয়ে নির্ভরযোগ্য সেশন শেষ হওয়ার সিগন্যাল যা বর্তমানে ব্যাপকভাবে উপলভ্য।

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