如何确保用户关闭网页时,关键数据100%发送到服务器?
电商平台需要记录用户离开前的最后浏览商品
在线文档工具要自动保存未提交的草稿
数据分析需捕获页面真实停留时长
但传统技术方案会失败:当页面卸载时,普通网络请求会被浏览器强制取消。下面通过真实案例,解析两种100%可靠的解决方案。
一、为什么普通请求会失败?
当用户关闭标签页,浏览器触发两个关键事件:
pagehide(页面隐藏)
unload(页面卸载)
此时若用fetch或XMLHttpRequest发送请求:
// 失败案例
window.addEventListener('unload', () => {
fetch('/save-data') // 请求将被浏览器中断
});根本原因:页面JS执行环境已被销毁,浏览器会主动终止未完成的请求。
二、过时的"暴力解法"及其危害
早期开发者使用同步XMLHttpRequest:
// 已淘汰的方案!
const xhr = new XMLHttpRequest();
xhr.open('POST', '/save', false); // 第三个参数false表示同步
xhr.send(data);致命缺陷:
完全阻塞主线程,导致页面卡死
用户无法切换标签页,甚至无法关闭浏览器
移动端可能触发系统警告
据统计,同步请求会使页面响应延迟300ms以上,用户体验极差。
三、现代方案一:navigator.sendBeacon()
这是浏览器专为"最后一刻"数据发送设计的api。
核心优势:
不阻塞页面关闭
浏览器保证请求发送
使用简单
代码示例:
window.addEventListener('pagehide', (event) => {
// 检查是否进入bfcache(页面未真正关闭)
if (event.persisted) return;
const logData = {
duration: performance.now(),
page: location.href
};
// 转换成Blob格式
const blob = new Blob(
[JSON.stringify(logData)],
{ type: 'application/json' }
);
// 发送请求(无需等待响应)
navigator.sendBeacon('/log', blob);
});注意事项:
| 特性 | 限制说明 |
|---|---|
| 请求方法 | 仅支持POST |
| 请求头 | 不可自定义 |
| 数据大小 | 部分浏览器限制在64KB以内 |
| 响应处理 | 无法获取服务器返回结果 |
四、现代方案二:Fetch API + keepalive
适合需要更多控制的场景:
window.addEventListener('pagehide', async (event) => {
if (event.persisted) return;
const draft = editor.getValue(); // 获取编辑器内容
// 使用keepalive模式
fetch('/api/save-draft', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content: draft }),
keepalive: true // 关键参数!
});
});与sendBeacon对比:
| 能力 | sendBeacon | fetch+keepalive |
|---|---|---|
| 支持GET请求 | ❌ | ✅ |
| 自定义Header | ❌ | ✅ |
| 发送FormData | ✅ | ✅ |
| 跨域凭证携带 | ❌ | ✅ |
五、如何选择最佳方案?
根据业务需求决策:
选 sendBeacon:
只需发送简单日志(如点击追踪/页面停留统计)选 fetch+keepalive:
需要保存用户数据(如文档草稿)、必须携带认证信息、需使用PUT/DELETE方法
六、真实场景中的避坑指南
事件监听优先用 pagehide
unload 事件可能阻止浏览器使用往返缓存(bfcache),影响用户体验重要数据需要双重保障
// 提前在visibilitychange事件中发送 document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') { navigator.sendBeacon('/backup-data'); } });服务器端必须幂等处理
因网络波动可能造成重复发送,服务端应做去重处理
七、特殊场景解决方案
场景1:需要发送超大数据
拆分数据 + 多次发送:
// 分段发送日志
const chunks = splitLargeData(data);
chunks.forEach(chunk => {
navigator.sendBeacon('/log-chunk', chunk);
});场景2:低版本浏览器兼容
if (!navigator.sendBeacon) {
// 回退方案:使用同步XHR(仅作保底)
const xhr = new XMLHttpRequest();
xhr.open('POST', url, false);
xhr.send(data);
}结语
通过sendBeacon和fetch+keepalive,开发者能在不影响用户体验的前提下,实现关闭页面的数据可靠上报。关键点在于:
优先选用浏览器原生方案
根据数据类型选择合适API
服务端做好数据幂等处理
最新统计显示,全球98%的浏览器已支持这两种方案,可放心用于生产环境。
实际部署前,建议使用Chrome DevTools的Application > Background services模块测试请求发送状态,确保万无一失。
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!