برای ردیابی مطمئن تجزیهوتحلیلها، «معیارهای کلیدی وب»، و دادههای دورسنجی که کل بازدید کاربر از صفحه وب را پوشش میدهد (نه فقط تا زمان بار شدن صفحه)، از fetchLater() API استفاده کنید.
fetchLater() API مطمئنترین راه برای ارسال داده به سرور در مواردی است که پاسخ لازم نیست و زمانبندی تحویل فوری نیست، که برای دادههایی مانند تجزیهوتحلیل کاربر، تلهمتری، ردیابی خطا، و سنجههای عملکرد مانند Core Web Vitals اعمال میشود.
تکنیکهای قدیمیتر مانند ایجاد پیکسل <img> در شنونده رویداد unload بهطور بدنامی غیرقابل اعتماد هستند (بهویژه در تلفن همراه) و میتوانند تأثیر منفی بر عملکرد داشته باشند (با غیرواجد شرایط کردن صفحات برای bfcache).
نحوه پیادهسازی
زمانبندی درخواست: بهمحض دردسترس قرار گرفتن دادههای مربوطه،
fetchLater()را با بار دادههایتان فراخوانی کنید. این کار دادهها را در صف قرار میدهد تا بعداً ارسال شود.بار داده را درصورت نیاز بهروز کنید: اگر کاربر دادههای بیشتری تولید کند یا وضعیت تغییر کند، درخواست زمانبندیشده قبلی را لغو کنید و
fetchLater()را دوباره با عکس آنی کاملاً بهروزرسانیشده فراخوانی کنید.بقیه کار را به مرورگر بسپارید: وقتی کاربر از صفحه خارج میشود یا برگه را میبندد، مرورگر بهطور مطمئن بار مفید را از جدیدترین فراخوانی
fetchLater()شما ارسال میکند.
کد نمونه
این کد مدت جلسه بازدید کاربر از صفحه را بااستفاده از fetchLater() اندازهگیری میکند تا هر ۱۰ ثانیه یک فانوسقه جدید را با مدت جلسه بهروزشده در صف قرار دهد.
const ANALYTICS_ENDPOINT = '/path/to/analytics/endpoint';
const sessionData = {
duration: 0,
id: crypto.randomUUID(),
};
let fetchLaterController = null;
function queueBeacon() {
// Abort any pending beacons before creating a new one.
if (fetchLaterController) {
fetchLaterController.abort();
}
fetchLaterController = new AbortController();
// Update session duration to the current page time.
sessionData.duration = performance.now();
// Schedule a fetch for the data payload to be sent later.
// IMPORTANT: wrap the call in a try/catch to handle quota errors.
try {
fetchLater(ANALYTICS_ENDPOINT, {
method: 'POST',
headers: {'content-type': 'application/json'},
body: JSON.stringify(sessionData),
signal: fetchLaterController.signal,
});
} catch (error) {
// Handle errors as needed.
}
}
// Update the session data and queue a new beacon every 10 seconds.
setInterval(queueBeacon, 10000);
روالهای مطلوب
- باید از
fetchLater()برای ارسال داده به سرور در هر شرایطی که پاسخ ضروری نیست و ارسال فوری دادهها حیاتی نیست استفاده کنید. - باید از
AbortControllerبرای لغو واکشی معلقه در مواردی که ممکن است لازم باشد دادهها قبلاز خروج کاربر از صفحه بهروزرسانی شوند استفاده کنید - اندازه بار مفید را به حداقل برسانید تا از سهمیه فراتر نرود (درحالحاضر تقریباً ۶۴ کیلوبایت برای هر مبدأ).
- باید فراخوانیهای
fetchLater()را درtry/catchقرار دهید تا خطاهای سهمیه را مدیریت کنید. - باید وجود
fetchLater()را درglobalThisشناسایی کنید و استراتژی جایگزینی برای مرورگرهایی که از این میانای برنامهسازی کاربردی پشتیبانی نمیکنند پیادهسازی کنید. - از شیء
ReadableStreamبرای بدنه درخواست استفاده نکنید، زیرا این کار باعث ایجاد خطا میشود.
پشتیبانی مرورگر و راهبردهای جایگزین
اگر fetchLater() به هدف «خط مبنا» شما نرسد، به استراتژی جایگزین نیاز است. بااینحال، با توجه به مزایای بهبود قابلیت اطمینان و عملکرد این میانای برنامهسازی کاربردی، اگر مرورگر از fetchLater() پشتیبانی میکند، باید از آن استفاده شود.
استراتژی جایگزین توصیهشده استفاده از شبیهساز مرورگر زیر است که بهطور داخلی مرورگرهای پشتیبانینشده را بااستفاده از fetch() با keepalive یا navigator.sendBeacon() مدیریت میکند. کد شما باید مستقیماً fetchLater() را فراخوانی کند — هرگز خودتان fetch()، sendBeacon()، یا دیگر APIهای فانوس را فراخوانی نکنید.
fetchLater() شبیهساز مرورگر
از شبیهساز مرورگر fetchLater() حداقلی زیر استفاده کنید که میانای برنامهسازی کاربردی را تا حد امکان در مرورگرهای پشتیبانینشده پیادهسازی میکند.
تنها تفاوت رفتاری قابلتوجه با این شبیهساز مرورگر این است که از visibilitychange برای تشخیص زمان خروج کاربر استفاده میکند، بهجای اینکه به مدیریت بارگیری بومی مرورگر متکی باشد. این جزئیات پیادهسازی داخلی است — کد شما نیازی به گوش دادن به visibilitychange یا هر رویداد چرخه حیات صفحه دیگری ندارد. کافی است fetchLater() را فراخوانی کنید تا شبیهساز مرورگر ارسال را انجام دهد.
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;
},
};
};