大多數數據分析、遙測和 Core Web Vitals 數據分析資料的優先順序都低於回應使用者,因此您可以放心地延遲傳送,直到使用者離開頁面為止。但如果需要提供即時更新,則不在此限,因為時效性很重要。
如要提供即時分析更新,同時盡可能減少信標,最佳做法是使用 fetchLater() 搭配 activateAfter 設定選項。這樣一來,您就能有效去抖動,並將指定時間範圍內發生的所有 Analytics 事件批次處理成單一信標,即使使用者在逾時前離開網頁,也能可靠地傳送。
導入方式
排定要求:只要有任何相關數據分析資料,請立即呼叫
fetchLater()並傳遞資料酬載和activateAfter值。系統會將資料排入佇列,並在指定時間過後或使用者提前離開網頁時傳送。批次處理多個事件:如果在
activateAfter逾時前發生新的數據分析事件,請取消先前排定的要求,並再次呼叫fetchLater()(使用相同的activateAfter值),在單一酬載中傳送完整的事件佇列。在逾時時間到期時重設事件佇列:如果排定的信標成功傳送後 (即
fetchLater()結果的activated值為true),發生新的數據分析事件,請重設事件佇列。讓瀏覽器處理其餘作業:如果使用者在
activateAfter逾時前離開或關閉分頁,瀏覽器仍會可靠地傳送最近一次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()配額 (目前每個來源約 64 KB)。 - 務必將
fetchLater()呼叫包裝在try/catch中,以便處理配額錯誤。 - 務必偵測
globalThis上是否有fetchLater(),並為不支援 API 的瀏覽器實作備用策略。 - 請勿使用
ReadableStream物件做為要求主體,否則會發生錯誤。
瀏覽器支援和備用策略
因此,通常需要備用策略。
不過,考量到這個 API 的可靠性和效能優勢,如果瀏覽器支援,建議使用 fetchLater()。
fetchLater() polyfill
請使用下列最小 fetchLater() polyfill,在不支援的瀏覽器中盡可能實作 API。
這個 polyfill 唯一值得注意的行為差異是,它不會在使用者離開頁面時傳送酬載,而是在頁面的 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;
},
};
};