摘要:埋点最怕"该记的数据没记到"。用户点击下单、页面随即关闭,普通请求在卸载阶段被浏览器取消,数据就丢了。sendBeacon 正是为"页面关闭时也能可靠送达"设计的上报方案。本文讲清它的原理、用法、兼容降级,以及怎么验证数据没丢。
先看一个数据层面的背景:第三方脚本已经是网页的"标配"。HTTP Archive《2025 Web Almanac》统计,全球超过 90% 的网页至少加载一个第三方资源,访问量最高的 1000 个站点桌面端第三方请求中位数达 129 个。这些第三方脚本大部分干的是同一件事:上报数据。而所有上报里,最容易丢的一类就是"发生在页面即将关闭那一瞬间的事件"——比如下单成功、点击外链、看完最后一个页面。
结论:页面关闭时的数据丢失,根因是浏览器在卸载阶段会取消未完成的网络请求。sendBeacon 专门解决这个问题:它让浏览器在页面卸载时以"尽力送达"的方式异步发送数据,不阻塞页面、不依赖请求是否完成。用法一句话:
navigator.sendBeacon(url, data),不支持时用 fetch keepalive 或本地暂存补发兜底。
一、数据是怎么丢的
用户点击按钮触发上报时,如果你用 XHR 或普通 fetch:
- 页面进入卸载流程(关闭标签页、刷新、跳转),浏览器会取消尚未完成的网络请求;
- 请求发出去了,但响应还没回来(甚至请求还没真正发出),数据就没了;
- 在
beforeunload/pagehide里同步发 XHR 能部分缓解,但会阻塞页面、体验差,且移动端并不可靠。
二、sendBeacon 为什么能送达
sendBeacon 由浏览器底层处理:它把数据交给浏览器后立即返回,浏览器在合适时机(含页面卸载后)把请求发出,不参与页面生命周期阻塞。特性要点:
| 特性 | 说明 |
|---|---|
| 异步不阻塞 | 调用后立即返回,不阻塞页面关闭 |
| 卸载阶段可靠 | 页面关闭/跳转时仍会发送 |
| 轻量载荷 | 适合小体积数据(事件、统计),不适合大文件 |
| 跨域支持 | 可发送到任意允许的端点,由浏览器处理凭证策略 |
// 页面关闭前上报,一行代码
const sent = navigator.sendBeacon(
'/api/track',
new Blob([JSON.stringify(event)], { type: 'application/json' })
);
if (!sent) {
// 队列满了或浏览器不支持,走降级
fallbackReport(event);
}
三、兼容与降级:不能"只信 sendBeacon"
sendBeacon 在现代浏览器中支持广泛(MDN 记录其为基础广泛可用的接口),但仍有边界:队列容量有限、部分 WebView 行为不一致、旧环境不支持。标准降级链:
- 首选:
navigator.sendBeacon(); - 次选:fetch 带
keepalive: true(语义相近,同样适合卸载期); - 兜底:把事件写入 localStorage/sessionStorage,下次进入页面时补发。
function report(event) {
const body = JSON.stringify(event);
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/track', body);
} else if (navigator.keepalive && window.fetch) {
fetch('/api/track', { method: 'POST', body, keepalive: true });
} else {
saveToLocalQueue(event); // 本地暂存,下次补发
}
}
四、怎么验证数据没丢
上线后做三件事确认有效性:
- 埋点对账:把页面关闭类事件(点击外链、关闭前停留时长)与"服务端日志里实际收到的条数"对比,计算丢失率;
- 场景实测:分别测"普通跳转、刷新、关闭标签页、移动端切后台"四种场景的送达情况;
- 总量观察:对比改造前后同一事件的接收量级,正常应显著上升(说明之前确实在丢)。
踩坑记录:现象——改用 sendBeacon 后,数据量反而"少了"。根因——服务端按"请求完成返回 200"统计接收,而 sendBeacon 是"浏览器尽力发送",服务端日志按响应计数会漏掉部分送达;同时页面在
beforeunload里同步发 XHR 的老代码没删,事件被重复发送被去重掉了。排查证据——对比网关访问日志(按请求到达计)与应用日志(按响应计)数量不一致。修复方式——服务端改按"请求到达"计数、幂等去重,并清理重复上报代码。经验:sendBeacon 的"送达"以服务端收到为准,统计口径要从"响应"改成"到达",否则你会误判它在丢数据。
FAQ
Q1:sendBeacon 能传多大的数据?
A:浏览器有配额限制(一般 64KB 量级,各浏览器不同),适合事件类小载荷;大数据应走分片或队列。
Q2:和 fetch keepalive 有什么区别?
A:语义相近,都是卸载期可用的上报手段;sendBeacon 更专注"发送",keepalive 是 fetch 的一个选项,能拿到响应状态。现代环境二者皆可。
Q3:一定要在卸载事件里调用吗?
A:不必须。日常事件直接用 sendBeacon 也可以;只有"关闭/跳转瞬间"的事件必须借助它才能在卸载时送达。
Q4:WebView / 小程序里能用吗?
A:依赖内核支持;不支持的 WebView 走本地暂存补发,或使用宿主环境提供的专用上报通道。
Q5:上报会不会被浏览器丢弃?
A:存在"尽力而为"的不确定性(如系统资源极端紧张),所以关键事件建议加本地暂存兜底,做到"能补则补"。
总结
页面关闭时丢数据,是埋点链路里最隐蔽的缺口之一:业务在增长,但"最后一步"一直没记全。sendBeacon 用一行代码补上这个缺口——异步、不阻塞、卸载期可靠送达;再配合 keepalive 与本地暂存两级降级,以及"按到达计数"的验证口径,就能让关闭类事件真正"颗粒归仓"。
参考资料:MDN Web Docs:sendBeacon 接口文档与浏览器兼容性数据;
HTTP Archive《2025 Web Almanac(Third Parties 章节)》;
WHATWG HTML 规范中关于卸载期请求行为的相关说明(经社区转引)。
276

被折叠的 条评论
为什么被折叠?



