如需可靠地跟踪涵盖用户对网页的整个访问过程(而不仅仅是网页加载过程)的分析、Core Web Vitals 和遥测数据,请使用 fetchLater() API。
在不需要响应且传送时间不紧急的情况下,fetchLater() API 是向服务器发送数据的最可靠方式,适用于用户分析、遥测、错误跟踪和性能指标(例如 Core Web Vitals)等数据。
众所周知,在 unload 事件监听器中创建 <img> 像素等旧技术并不可靠(尤其是在移动设备上),并且可能会对性能产生负面影响(导致网页不符合 bfcache 的条件)。
如何实现
安排请求:一旦有相关数据可用,请立即使用数据载荷调用
fetchLater()。这会将数据排入队列,以便稍后发送。根据需要更新载荷:如果用户生成了更多数据或状态发生了变化,请中止之前安排的请求,然后使用完全更新的快照再次调用
fetchLater()。让浏览器处理其余工作:当用户离开或关闭标签页时,浏览器会可靠地发送您最近一次
fetchLater()调用的载荷。
示例代码
此代码使用 fetchLater() 每 10 秒将一个包含更新后会话时长的新信标加入队列,从而衡量用户访问网页的会话时长。
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取消待处理的提取操作 - 务必尽量减小载荷大小,以免超出配额(目前每个源大约 64KB)。
- 务必将对
fetchLater()的调用封装在try/catch中,以处理配额错误。 - 务必检测
globalThis上是否存在fetchLater(),并针对不支持该 API 的浏览器实施回退策略。 - 请勿在请求正文中使用
ReadableStream对象,否则会出错。
浏览器支持和后备策略
如果 fetchLater() 未达到基准目标,则需要采用后备策略。不过,鉴于此 API 具有更高的可靠性和性能优势,如果浏览器支持,则应使用 fetchLater()。
建议的后备策略是使用以下 polyfill,它会在内部使用 fetch() 搭配 keepalive 或 navigator.sendBeacon() 来处理不支持的浏览器。您自己的代码必须直接调用 fetchLater(),切勿自行调用 fetch()、sendBeacon() 或其他信标 API。
fetchLater() polyfill
请使用以下最小 fetchLater() polyfill,它会在不支持的浏览器中尽可能接近地实现该 API。
此 polyfill 唯一显著的行为差异在于,它使用 visibilitychange 来检测用户何时离开,而不是依赖于浏览器的原生卸载处理。这是内部实现细节,您的代码无需监听 visibilitychange 或任何其他页面生命周期事件。只需调用 fetchLater(),polyfill 就会处理传送。
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;
},
};
};