sendBeacon() 并非绝对可靠,仅保证数据成功加入浏览器发送队列,不保证服务器接收;失败场景包括强制杀进程、网络中断、跨域未配CORS、队列满(约64KB)等。

为什么 sendBeacon() 在页面卸载时仍可能失败
它不是“绝对可靠”的兜底方案——navigator.sendBeacon() 只保证请求被浏览器**排入发送队列**,不保证服务器一定能收到。常见失败场景包括:用户强制杀进程(非正常关闭)、网络瞬间断开、目标 URL 被浏览器拦截(如跨域且未配 CORS)、或 Beacon 队列已满(Chrome 限制约 64KB 缓冲区)。
关键点在于:它依赖浏览器底层异步发送机制,在 beforeunload 或 pagehide 中调用后,JS 主线程会继续执行,但页面资源很快被回收,后续无法补救。
- 不要在
beforeunload里做复杂计算或等待 Promise —— 主线程可能被中断 - 避免多次调用
sendBeacon()同一 URL;重复调用不会排队,后一次会覆盖前一次 - 务必使用
POST方法,且 payload 类型必须是ArrayBuffer、Blob或FormData;传string或JSON对象会静默失败
正确构造 Beacon 数据并触发上报
最简可用模式是把埋点数据序列化为 URLSearchParams 或 FormData,再转成 Blob(兼容性最好)。直接传 JSON.stringify() 字符串会失败,因为浏览器只接受二进制类 payload。
示例(推荐):
function reportExit(data) {
const params = new URLSearchParams(data);
const blob = new Blob([params], { type: 'application/x-www-form-urlencoded' });
navigator.sendBeacon('/log', blob);
}
// 页面隐藏或卸载时触发
window.addEventListener('pagehide', () => {
reportExit({
event: 'page_exit',
url: window.location.href,
timestamp: Date.now()
});
});
-
sendBeacon()第二个参数不能是普通对象或字符串,必须是二进制兼容类型 - 路径
/log必须是同源地址,否则浏览器直接拒绝(不报错,也不发请求) - 服务端需能接收
application/x-www-form-urlencoded格式,且不依赖 session 或 cookie(因页面已销毁,部分浏览器会清空凭据)
如何判断 Beacon 是否真的发出去了
浏览器不提供成功回调,也无法监听 Beacon 请求状态。唯一可行的验证方式是:在服务端记录接收到的请求,并与前端日志比对。前端可配合轻量 fallback 机制提升可观测性:
- 在
visibilitychange事件中提前捕获visibilityState === 'hidden',此时仍有完整执行环境,可尝试一次普通fetch()(带keepalive: true),失败再降级到sendBeacon() - 对关键埋点(如支付完成),可在页面内留一个隐藏 iframe,用
src触发 GET 上报(虽不精准,但有更高到达率) - 禁止在
unload事件中使用sendBeacon()—— 该事件在多数现代浏览器中已被禁用或不可靠,应优先用pagehide
服务端接收 Beacon 请求的注意事项
Beacon 请求由浏览器后台发起,没有 Referer、不携带部分 Cookie(尤其第三方上下文),且超时策略与普通请求不同。Node.js/Python/Java 等后端常因默认配置丢弃这类请求:
- 确保反向代理(如 Nginx)未设置过短的
client_header_timeout或client_body_timeout;Beacon 可能延迟数秒才真正发出 - Spring Boot 需显式允许空 Referer:
allowed-origins: "*"+allow-credentials: false;否则预检失败 - PHP 的
input_get_contents()可读取原始 body,但若用了$_POST,需确认 Content-Type 是浏览器实际发送的类型(application/x-www-form-urlencoded或text/plain)
最易忽略的是:Beacon 请求的 User-Agent 可能被简化,且无 Sec-Fetch-* 头,服务端日志规则若强依赖这些字段,会导致漏记。

















