navigator.sendbeacon() 能稳定上报卸载日志,因其不依赖 promise 与事件循环,而是将数据交由浏览器底层网络栈“尽力送达”;需在 pagehide 等时机调用,传 urlsearchparams 等合规格式,后端适配 text/plain 解析并做幂等处理。

在页面卸载(如用户关闭标签页、跳转、刷新)时,常规的异步请求(如 fetch 或 XMLHttpRequest)很可能被浏览器中止,导致日志上报失败。而 navigator.sendBeacon() 是浏览器专为此场景设计的 API:它在页面即将卸载时**异步发送数据且不阻塞卸载流程**,且即使 Promise 已 resolve/reject,只要调用过 sendBeacon,数据仍会尽力发出。
为什么 sendBeacon 能绕过 Promise 终止问题
sendBeacon 不返回 Promise,也不依赖事件循环;它把数据交给浏览器底层网络栈后立即返回 true/false,由浏览器在后台「尽力送达」——这和 Promise 的生命周期完全解耦。即使当前 JS 执行栈已清空、Promise 已被 GC 回收,只要数据已交由 sendBeacon,就不会因页面卸载而丢失。
如何正确使用 sendBeacon 上报日志
- 只支持
POST方法,且数据类型限于ArrayBufferView、Blob、FormData、URLSearchParams或纯字符串(不能是普通对象) - 必须在
beforeunload、pagehide或visibilitychange(hidden 状态)等卸载相关时机调用 - 推荐优先监听
pagehide(兼容性更好,且能捕获 iOS Safari 后台标签页冻结) - 上报前应序列化日志为字符串或构造
URLSearchParams,避免直接传对象
一个健壮的日志上报封装示例
以下代码兼顾兼容性、序列化与降级:
function reportLog(logData) {
const url = '/api/log';
const body = new URLSearchParams();
Object.entries(logData).forEach(([k, v]) => {
body.append(k, typeof v === 'object' ? JSON.stringify(v) : String(v));
});
// 尝试 sendBeacon
if (navigator.sendBeacon && navigator.sendBeacon(url, body)) {
return true;
}
// 降级:仅用于调试(实际卸载时 fetch 极大概率失败)
if (process.env.NODE_ENV === 'development') {
fetch(url, {
method: 'POST',
body,
keepalive: true // 实验性,部分浏览器支持,但不如 sendBeacon 可靠
});
}
return false;
}
// 在 pagehide 时触发
window.addEventListener('pagehide', () => {
reportLog({
type: 'page_exit',
timestamp: Date.now(),
url: window.location.href,
referrer: document.referrer
});
});
注意事项与常见坑
-
sendBeacon的请求头固定为Content-Type: text/plain(除非传Blob指定 type),后端需适配解析方式 - 不要在
beforeunload中弹窗(如return 'xxx'),会干扰 sendBeacon 执行,尤其在 Safari 中 - 单次 sendBeacon 数据大小建议 ≤ 64KB(Chrome 限制),超长日志需截断或分批
- 无法获取响应结果(无回调、无 status),需后端记录接收日志并做幂等处理
本质上,sendBeacon 不是“让 Promise 继续运行”,而是彻底跳出 Promise 模型——用浏览器原生机制保障最后时刻的数据发出。只要数据格式合规、时机正确、后端配合,就能稳定捕获页面卸载前的关键日志。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











