sendBeacon常发不出去的根本原因是浏览器对其执行时机和资源限制极严:仅在卸载瞬间尝试发送,不等待响应、不触发CORS预检、不参与优先级调度,且遇主线程阻塞或快速跳转即静默丢弃。

页面卸载时 sendBeacon 为什么经常发不出去
根本原因不是 API 写错了,而是浏览器对 navigator.sendBeacon() 的执行时机和资源限制极为严格:它只在页面卸载(unload / beforeunload)的极短时间内尝试发送,且不等待响应、不触发 CORS 预检、不参与 fetch 优先级调度。一旦用户快速关闭标签页、刷新、导航跳转,或主线程被阻塞(比如同步 XMLHttpRequest、长任务),Beacon 就会静默丢弃——连失败回调都没有。
常见误用场景:
- 在
beforeunload里调用sendBeacon()后还执行耗时 JS(如计算、日志打印),导致 Beacon 被截断 - 传入非
ArrayBuffer/Blob/FormData/URLSearchParams类型(比如直接传JSON.stringify(obj)字符串),触发静默失败 - 目标 URL 是跨域且未配
Access-Control-Allow-Origin: *(虽然 Beacon 不校验 CORS,但服务端仍需允许接收 POST)
sendBeacon 必须用 Blob 或 FormData 才可靠
sendBeacon() 对数据类型敏感:传字符串(哪怕 content-type 正确)在部分 Chrome 版本中会失败;传 Object 直接报错。唯一稳妥路径是封装为 Blob 或 FormData。
推荐做法(兼容性好、无编码歧义):
立即学习“前端免费学习笔记(深入)”;
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
- 用
new Blob([JSON.stringify(data)], {type: 'application/json'})包裹 JSON 数据 - 服务端必须能接收 raw body(如 Node.js 的
express.raw({ type: 'application/json' })) - 避免用
FormData上传 JSON——它会变成multipart/form-data,后端解析成本高且不必要
示例:
const data = { page: '/home', duration: 12400, timestamp: Date.now() };
const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
navigator.sendBeacon('/log', blob);
别依赖 beforeunload —— 改用 visibilitychange + 页面隐藏前预上报
beforeunload 触发不可靠,且现代浏览器(Chrome 88+)已限制其执行时长。更可行的策略是监听页面可见性变化,在用户切走或最小化前主动发一次 Beacon,把关键指标“抢发”出去。
操作要点:
- 监听
document.addEventListener('visibilitychange', ...) - 当
document.hidden === true时立即调用sendBeacon() - 搭配
pagehide事件兜底(比unload更早触发,且支持event.persisted判断是否是缓存导航) - 避免重复发送:用
sessionStorage记录是否已上报,防止多次切出触发多次
如何验证 sendBeacon 是否真发出去了
浏览器开发者工具的 Network 面板默认不显示 Beacon 请求(因它不参与主请求生命周期)。必须手动开启:
- 打开 DevTools → Network → 勾选
Preserve log - 在 Filter 框输入
beacon或目标 URL 路径 - 右键表头 → 勾选
Initiator列,确认来源是sendBeacon - 服务端务必记录原始 request body 长度和 headers(特别是
Content-Type),因为前端静默失败时,服务端根本收不到任何请求
真正难的不是写那行 sendBeacon(),而是确保它在页面消失的 100ms 内完成序列化、排队、底层 socket 发送——任何环节卡住,数据就永远消失了。

















