前端知库

页面关闭时怎样可靠上报?sendBeacon 和 keepalive 怎么选?

在 visibilitychange 变为 hidden 时上报,而不是依赖 unload/beforeunload;sendBeacon 只发 POST、只返回是否入队;fetch keepalive 可自定义请求并读响应;fetchLater 由浏览器在页面离开或超时时代发,目前仅 Chromium 支持。

参考答案

简答

  • 上报时机选 visibilitychange 且 visibilityState === 'hidden';unload、beforeunload 在移动端常常不触发,unload 还会妨碍 bfcache。
  • sendBeacon 与 fetch 的 keepalive 共用 64 KiB 配额,超额时互相回退也会失败;要平时分批 flush、控制单次体积。两者的细节见《sendBeacon 返回 true 就代表上报成功了吗?页面离开时怎样发送统计?》。
  • fetchLater 让浏览器在页面离开或 activateAfter 超时后代为发送,适合"先登记、最后再发",但仍是实验性 API,目前只有 Chromium 系浏览器支持,必须做特性检测和降级。

上报时机

JavaScript
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') flush();
});

用户切到其他应用后再从任务管理器杀掉浏览器时,unload 根本没有机会执行,而 visibilitychange 在切走时就已触发。hidden 不一定意味着离开,用户可能回来,所以 flush 只发送当前缓冲的数据,后续数据继续累积。

pagehide 与 bfcache 兼容,但可靠性问题和 unload 类似,MDN 推荐优先使用 visibilitychange。bfcache 的细节见《bfcache 与 HTTP 缓存有什么区别?》。

三种方式怎么选

方式方法读响应体积限制兼容性
sendBeacon仅 POST否与 keepalive 共享 64 KiB广泛支持
fetch + keepalive任意可以与 sendBeacon 共享 64 KiB广泛支持
fetchLater任意否有配额,超出抛 QuotaExceededError仅 Chromium

sendBeacon 返回 false 多半是配额已满,这时改用 keepalive 同样会失败。可靠做法是平时按数量或时间分批 flush,离开时只发剩余的小块数据;keepalive 只作为不支持 sendBeacon 环境的回退。返回值、Content-Type 与预检的细节见《sendBeacon 返回 true 就代表上报成功了吗?页面离开时怎样发送统计?》。

fetchLater

JavaScript
let controller;
function schedule(data) {
  if (!('fetchLater' in window)) return; // 降级到 visibilitychange + sendBeacon
  controller?.abort(); // 数据更新时取消旧请求再重新登记
  controller = new AbortController();
  try {
    fetchLater('/collect', {
      method: 'POST',
      body: JSON.stringify(data),
      signal: controller.signal,
      activateAfter: 60_000,
    });
  } catch (error) {
    if (error.name !== 'QuotaExceededError') throw error;
  }
}

它的价值在于无需抓准离开时机:页面被销毁或进入 bfcache 时,浏览器会发出已登记的请求,不依赖离开事件是否触发。限制包括:只接受 HTTPS 等可信 URL,请求体不能是流,响应会被丢弃,受 CSP connect-src 约束。

常见追问

  • 为什么不在 beforeunload 里用同步 XHR?同步请求阻塞页面离开,Chrome 等浏览器已禁止在页面卸载期间发送同步 XHR,体验和可靠性都差。
  • 数据会不会重复?hidden 可能触发多次,用事件 ID 去重,服务端也要能处理重复。
  • 上报接口要带 Cookie 吗?尽量用 URL 参数或请求体里的匿名 ID,减少跨站 Cookie 带来的限制。