
本文介绍使用 fetch 的 keepalive 选项,在 beforeunload 事件中异步发送关键请求(如会话结束上报),无需用户确认、不阻塞跳转,且请求能真正发出并送达服务器。
本文介绍使用 `fetch` 的 `keepalive` 选项,在 `beforeunload` 事件中异步发送关键请求(如会话结束上报),无需用户确认、不阻塞跳转,且请求能真正发出并送达服务器。
在 Web 开发中,常需在用户离开页面前执行清理操作,例如上报用户会话结束、保存草稿或释放后端资源。然而,直接在 window.onbeforeunload 或 window.addEventListener('beforeunload') 中调用常规 fetch() 会导致请求被浏览器立即终止——因为标准 fetch 是“可中断”的,一旦导航开始(如刷新、关闭标签页、跳转链接),未完成的请求会被强制中止,即使已进入网络栈。
根本原因:beforeunload 事件的设计目标是“提示用户是否离开”,而非执行异步任务;浏览器为保障用户体验与性能,会主动取消所有非关键网络请求,防止页面卸载被延迟。
✅ 正确解法:启用 fetch 的 keepalive 选项。
keepalive: true 告诉浏览器:该请求具有高优先级,即使页面正在卸载,也应尽力将其发送至服务器(通过后台信标机制实现)。该特性已获现代浏览器广泛支持(Chrome 52+、Firefox 53+、Edge 79+、Safari 11.1+),且不会触发任何弹窗提示,完全静默运行。
以下是推荐的实现方式:
window.addEventListener('beforeunload', (event) => {
// 注意:此处不能 await,也不能 return Promise
// keepalive 请求必须同步发起,且不可依赖响应
fetch('/api/endSession', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ reason: 'user_navigate' }),
keepalive: true // ✅ 关键:启用后台持久化发送
})
.catch(err => {
// 仅用于调试;生产环境通常无需处理(失败无回调)
console.warn('Session end request failed (keepalive):', err);
});
// ⚠️ 注意:不要在此处 return 非空字符串(否则触发默认提示框)
// 若需兼容旧逻辑,请确保只在必要时 return 字符串,且与 keepalive 分离
});? 重要注意事项:
- keepalive 请求不可携带 Authorization 头或 Cookie(若 credentials: 'include'),除非目标域名明确支持 CORS 并允许对应头字段(因卸载时上下文受限);
- 请求体大小建议控制在 64KB 以内(部分浏览器存在限制);
- 服务端无法保证该请求“一定成功”——它可能因网络中断、DNS 失败或服务器无响应而丢失,因此应设计为幂等操作(如 POST /api/endSession 应支持重复提交);
- 不要试图 await fetch(...) 或在 beforeunload 中使用 async/await:事件处理器必须同步返回,否则行为不可预测;
- 避免在 onbeforeunload 赋值写法中混用箭头函数与 return(易引发语法错误),推荐统一使用 addEventListener。
? 进阶建议:对关键业务(如支付退出、表单提交),可结合 navigator.sendBeacon() 作为降级方案(兼容性更广,但仅支持 POST 且只能发送 ArrayBufferView/Blob/FormData/URLSearchParams)。不过,对于大多数 JSON API 场景,keepalive + fetch 已是更简洁、语义更清晰的选择。
总结:keepalive: true 是现代浏览器为解决“页面卸载前可靠上报”问题提供的标准化方案。正确使用它,即可在不干扰用户、不增加交互负担的前提下,显著提升前端埋点、会话管理与资源清理的可靠性。










