告别传统方法:在关闭浏览器标签前可靠发送 HTTP 请求
在前端开发中,我们常常遇到这样一个需求:用户即将关闭页面或标签页时,需要向服务器发送最后一条数据。比如:
- 页面停留时间统计
- 日志或行为数据上传
- 用户草稿自动保存
然而,看似简单的操作在实践中却充满挑战。
传统方法为何不可靠?
当用户关闭页面时,浏览器会触发 pagehide
和 unload
等卸载事件。此时:
- 标准的异步请求(fetch 或 XMLHttpRequest)可能会被浏览器中断
- 页面卸载后,JavaScript 执行环境消失,请求可能直接取消
过去的解决方案是 同步 XMLHttpRequest:
const xhr = new XMLHttpRequest();
xhr.open('POST', '/log', false); // false 表示同步请求
xhr.send(JSON.stringify({ event: 'close_tab' }));
❌ 缺点:
- 阻塞主线程
- 页面 UI 卡顿
- 用户体验极差
现代方案一:navigator.sendBeacon()
sendBeacon()
是 W3C 专门为“页面卸载时发送数据”设计的 API:
window.addEventListener('pagehide', (event) => {
if (event.persisted) return; // 页面进入 bfcache,不发送数据
const analyticsData = {
timeOnPage: Math.round(performance.now()),
lastAction: 'close_tab',
};
const blob = new Blob([JSON.stringify(analyticsData)], {
type: 'application/json; charset=UTF-8',
});
const success = navigator.sendBeacon('/log-analytics', blob);
if (success) {
console.log('分析日志已成功加入发送队列。');
} else {
console.error('无法发送分析日志。');
}
});
✅ 特点:
- 异步非阻塞:不影响页面关闭速度
- 浏览器保证发送:即使页面已关闭
- 限制:仅支持 POST 请求,不能自定义请求头
适用场景:日志、统计、行为数据上传等轻量级数据。
现代方案二:fetch({ keepalive: true })
对于需要更多灵活性(如 PUT 方法或自定义请求头)的场景,可以使用 fetch 的 keepalive
选项:
window.addEventListener('pagehide', (event) => {
if (event.persisted) return;
const draftContent = document.getElementById('editor').value;
if (!draftContent) return;
const draftData = {
content: draftContent,
timestamp: Date.now(),
};
try {
fetch('/api/drafts/save', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(draftData),
keepalive: true, // 关键!
});
console.log('保存草稿的请求已提交。');
} catch (e) {
console.error('提交保存草稿请求时发生错误:', e);
}
});
✅ 特点:
- 支持多种 HTTP 方法(POST/PUT 等)
- 允许设置请求头
- 异步非阻塞,保证用户体验
- 与 sendBeacon 类似,页面关闭后请求仍会继续
如何选择?
需求类型 | 推荐方案 |
---|---|
日志/统计/行为数据上传 | navigator.sendBeacon() |
更新资源/PUT请求/自定义头 | fetch({ keepalive: true }) |
总结:
- 传统同步请求阻塞主线程,用户体验差
sendBeacon
和fetch({ keepalive: true })
可以在 不阻塞用户体验 的前提下,可靠地发送最后一条数据