如何在页面卸载前可靠发送 HTTP 请求(避免请求被取消)

2次阅读

如何在页面卸载前可靠发送 HTTP 请求(避免请求被取消)

本文介绍使用 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 是现代浏览器为解决“页面卸载前可靠上报”问题提供的标准化方案。正确使用它,即可在不干扰用户、不增加交互负担的前提下,显著提升前端埋点、会话管理与资源清理的可靠性。

text=ZqhQzanResources