keepalive: true 是唯一能让 POST/PUT/DELETE 请求在页面卸载后尽力发出的机制,但需满足方法限制、同步调用、序列化 body 等硬性条件,且无回调。

keepalive: true 不是“奇效”,而是一种有明确边界、需严格配合使用场景的浏览器机制。它不能保证请求100%到达服务端,但确实是当前唯一能让 POST/PUT/DELETE 类请求在页面卸载后仍被浏览器尽力发出的方式。
为什么普通 fetch 在页面关闭时大概率失败
页面触发导航(如点击链接、调用 window.location.href =)、刷新或关闭标签页时,JavaScript 执行环境会快速终止。此时:
- 未完成的 fetch 请求会被强制中止,无论是否已发到网络层
- XMLHttpRequest 同样失效;同步 XHR 虽能发出去,但会阻塞页面、触发浏览器警告,已被现代浏览器限制
- await、.then()、setTimeout 回调等异步逻辑全部丢弃,无法监听结果
keepalive: true 的真实作用与硬性要求
它不是魔法开关,而是向浏览器网络栈提交一个“后台发送任务”。生效必须同时满足:
- 必须是带 body 的请求:仅支持 POST / PUT / DELETE;GET 不允许设 keepalive(语法报错)
- 必须在卸载前同步调用:只能在 beforeunload、pagehide 或跳转前的同步代码块中执行,不可 await,不可包装成 Promise
- 不返回可等待的 Promise:调用即“递交”,后续无成功/失败回调,catch 仅用于调试,生产环境通常忽略
- body 数据需已序列化:如 JSON.stringify()、FormData、Blob 等,不能传原始对象
和 sendBeacon 对比:选哪个更稳?
两者目标一致,但行为细节不同:
- sendBeacon:专为卸载设计,兼容性稍弱(IE 不支持),数据必须是二进制兼容类型(Blob、FormData、URLSearchParams),且只支持同源 URL
- fetch + keepalive:支持跨域(需服务端配 CORS),能传标准 JSON,但对请求方法和调用时机更敏感
- 实践中建议降级使用:优先 try navigator.sendBeacon,失败则 fallback 到 fetch(..., { keepalive: true })
一个可用的埋点上报函数示例
以下代码兼顾可靠性与简洁性,适用于页面退出类事件(如离开页面、关闭标签):
function reportExit(data) {
const payload = JSON.stringify({
...data,
timestamp: Date.now(),
url: location.href
});
// 优先用 sendBeacon
if (navigator.sendBeacon) {
const blob = new Blob([payload], { type: 'application/json' });
return navigator.sendBeacon('/api/log', blob);
}
// 降级:fetch + keepalive(注意:必须同步调用)
try {
fetch('/api/log', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: payload,
keepalive: true
});
return true;
} catch {
return false;
}
}
// 绑定 pagehide(比 beforeunload 更可靠,尤其移动端)
window.addEventListener('pagehide', () => {
reportExit({ event: 'page_exit', reason: 'user_close_tab' });
});


















