← 返回题目列表

页面卸载时如何可靠上报?sendBeacon 和 keepalive 有什么区别?

高频 中等 第 8 / 26 题 更新于 2026/07/29
sendBeaconkeepalive埋点上报前端网络

简化版

页面卸载时普通异步请求容易被浏览器取消,统计和埋点常用 navigator.sendBeacon()fetch(..., { keepalive: true })sendBeacon 适合少量 POST 上报且不关心响应;keepalive 更像 Fetch 语义,但也有请求体大小和生命周期限制。

详细版

典型用法:

window.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    navigator.sendBeacon('/log', JSON.stringify({ duration: 120 }))
  }
})

相比 beforeunload 中强行同步请求,visibilitychange 更适合作为页面进入后台的信号。sendBeacon 会让浏览器在不阻塞页面卸载的情况下尝试发送数据;它不能读取响应,也不适合大 payload 或关键业务提交。

完整版教学

一、为什么卸载上报容易丢

用户关闭标签、跳转页面、刷新或移动端切后台时,页面的 JS 执行环境很快会被冻结或销毁。普通 fetch 还没发完,浏览器可能直接取消。

click link
  -> 页面开始导航
  -> 普通异步 fetch 还在 pending
  -> 旧页面上下文销毁
  -> 请求可能被中断

记忆钩子:卸载上报的目标不是“等响应”,而是“别挡用户离开,还尽量把小数据送出去”。

二、sendBeacon 的定位

navigator.sendBeacon('/analytics', new Blob([
  JSON.stringify({ page: '/home', stay: 35 }),
], { type: 'application/json' }))

sendBeacon 专门用于少量遥测数据上报。它返回布尔值表示浏览器是否成功把数据排队,不表示服务端已经成功处理。它通常使用 POST,不提供读取响应体的能力。

这正好符合埋点需求:页面停留 35 秒、点击了 3 次按钮、即将离开时上报,不应该为了等日志响应阻塞跳转。

三、为什么推荐 visibilitychange

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    flushLogs()
  }
})

unloadbeforeunload 在现代浏览器中限制越来越多,还可能影响 bfcache。visibilitychange 在页面变为隐藏时触发,覆盖切后台、切标签、导航等更多实际场景。

如果用户只是切到另一个 App,页面未必立即卸载,但已经是一个很好的上报时机。移动端尤其要重视这个信号。

四、fetch keepalive 的特点

fetch('/log', {
  method: 'POST',
  body: JSON.stringify({ event: 'leave' }),
  headers: { 'Content-Type': 'application/json' },
  keepalive: true,
})

keepalive 允许请求在页面卸载后继续一小段时间。它保留了 Fetch 的写法,可以设置方法、头部等,但浏览器会限制请求体大小和数量,不能当成普通后台任务。

能力sendBeaconfetch keepalive
读取响应不支持语义上是 Fetch,但卸载后不应依赖响应
适合场景埋点、统计小型上报、需要自定义头时
阻塞卸载不阻塞不应阻塞
大文件上传不适合不适合

五、请求大小和可靠性边界

浏览器会对 beacon/keepalive 的待发送数据设置限制,常见经验是只放几十 KB 以内的小数据。假设一次日志 2KB,一次性上报 10 条约 20KB,一般合理;如果把 2MB 错误上下文塞进去,就不适合。

这些 API 提供“更可靠的尽力发送”,不是事务保证。网络断开、进程被杀、浏览器策略限制都可能导致失败。关键业务例如支付、下单、保存表单,不能依赖卸载上报完成。

六、工程实践

更稳的做法是平时分批上报,卸载时只 flush 少量剩余数据。埋点队列可以按数量、时间、页面隐藏事件触发发送。

用户操作 -> 进入队列
每 5 秒或满 20 条 -> 批量上报
visibility hidden -> flush 剩余小批量

同时要做脱敏和限流,避免把 token、手机号、完整表单内容等敏感数据塞进日志。日志上报不是安全豁免区。

七、常见误区与追问

  • 误区:在 beforeunload 里 await fetch 就可靠。 页面卸载不会等你的异步任务完成。
  • 误区:sendBeacon 返回 true 表示服务端收到。 它只表示浏览器接受排队请求。
  • 误区:卸载上报适合关键业务。 它适合统计日志,不适合支付、保存等强一致操作。
  • 追问:为什么用 visibilitychange 它覆盖页面隐藏和移动端切后台,且比 unload 更友好。
  • 追问:keepalive 能传大文件吗? 不能,浏览器会限制大小和生命周期。
  • 追问:如何提升可靠性? 平时批量上报,隐藏时 flush 小队列,服务端去重。

八、加强记忆

页面卸载上报要记“早发、小发、别等回包”:尽量在平时批量发送,不把所有日志压到离开瞬间;单次 payload 保持小;离开时用 sendBeaconkeepalive 尽力发送,不依赖响应。它解决的是埋点可靠性,不是关键业务提交。