页面unload事件触发时请求未发送,使用navigator.sendBeacon也无效
问题核心原因
- 异步请求在页面卸载场景会被强制终止:
unload事件触发时浏览器会立即销毁页面上下文,所有 pending 状态的异步XHR/fetch请求都会被直接终止,你原逻辑中用的await callEndpoint属于异步请求,还没完成发送就被中断,自然无法到达服务端。 - useEffect清理逻辑错误:你将
removeEventListener写在forEach的回调内部,这个return是forEach回调的返回值,不会被useEffect识别为全局清理函数,会导致事件监听器泄漏、重复注册的问题。 - sendBeacon调用姿势错误:sendBeacon仅支持POST请求,参数仅支持FormData、Blob、字符串三种类型,不能直接传入普通JSON对象,且默认请求头不为
application/json,如果服务端对请求头/格式有校验就会接收失败。
修复方案
1. 修正事件监听器注册逻辑
首先修复useEffect的事件绑定、清理逻辑,避免闭包和监听器泄漏问题:
useEffect(() => { if (!events) return; // 收集所有监听器的清理函数,统一执行 const cleanupList = []; events.forEach((event) => { const handler = () => { // 单独处理页面卸载类事件 if (['unload', 'beforeunload', 'pagehide'].includes(event.type)) { // 构造业务请求参数 const requestData = JSON.stringify({ // 填写你的业务请求参数 }); // 封装为Blob,设置JSON格式的请求头,兼容服务端参数校验 const blobData = new Blob([requestData], { type: 'application/json' }); const fullUrl = `${api}${event.listener}`; // sendBeacon由浏览器保证在页面卸载前完成发送,无需等待返回 const sendSuccess = navigator.sendBeacon(fullUrl, blobData); // 如果sendBeacon发送失败(比如超过64KB大小限制),降级用fetch+keepalive if (!sendSuccess) { fetch(fullUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', // 其他自定义请求头 }, body: requestData, keepalive: true // 告诉浏览器在页面卸载后继续完成请求 }); } return; } // 普通事件保留原逻辑 handleEvent(api, event); }; window.addEventListener(event.type, handler, event.options); // 存入清理函数列表 cleanupList.push(() => window.removeEventListener(event.type, handler, event.options)); }); // 全局清理函数 return () => cleanupList.forEach(clean => clean()); // 补充完整依赖,不要随意关闭eslint校验,避免闭包问题 }, [events, api]);
2. 额外优化建议
- 优先使用
pagehide事件代替unload:W3C已经不推荐使用unload事件,移动端Safari等浏览器在很多场景不会触发unload,pagehide的兼容性和可靠性更高,你可以直接修改事件数组中的type字段为pagehide。 - 不要在卸载类事件中执行任何异步逻辑、DOM操作、弹窗操作,这些行为都会被浏览器强制拦截。
内容的提问来源于stack exchange,提问作者Yumie
相关产品推荐
相关产品推荐

