
Safari 浏览器不等待 beforeunload 事件中的异步请求完成,导致数据无法及时提交;使用 navigator.sendBeacon() 可绕过该限制,在页面卸载前异步、可靠地发送轻量级数据。
safari 浏览器不等待 `beforeunload` 事件中的异步请求完成,导致数据无法及时提交;使用 `navigator.sendbeacon()` 可绕过该限制,在页面卸载前异步、可靠地发送轻量级数据。
在 Vue.js 应用中,我们常需在用户关闭标签页、刷新或跳转前保存临时编辑状态(如表单草稿、用户行为日志等)。虽然 window.addEventListener('beforeunload') 看似是理想选择,但 Safari(以及部分新版 Chrome/Firefox)出于性能与用户体验考虑,会立即卸载页面,不等待 fetch 或 XMLHttpRequest 等异步操作完成——这意味着你调用的 axios.post('/save', data) 或 fetch('/save', { method: 'POST', body: JSON.stringify(data) }) 很可能被静默中止,数据丢失。
✅ 正确解法:使用 navigator.sendBeacon()
sendBeacon() 是 W3C 标准 API,专为“页面卸载前发送少量数据”而设计。它以异步、非阻塞方式将数据(仅支持 ArrayBufferView、Blob、FormData、URLSearchParams 或 USVString)发送至指定 URL,且浏览器保证在页面完全卸载前尽力发送成功(即使窗口已关闭,请求仍会在后台完成)。
以下是 Vue 3 组合式 API 中的推荐实现:
// 在 setup() 或 onBeforeUnmount 中注册(注意:beforeunload 是全局事件,建议统一管理)
onBeforeUnmount(() => {
// 清理逻辑(可选)
});
// 监听 beforeunload 并触发 beacon
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
const data = new URLSearchParams();
data.append('draft', JSON.stringify(store.currentDraft)); // 示例:从 Pinia/Vuex 获取数据
data.append('timestamp', Date.now().toString());
// ✅ 关键:sendBeacon 自动处理跨域、序列化与发送时机
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/save-draft', data);
} else {
// 兜底方案(仅作提示,实际几乎无法保证发送成功)
console.warn('sendBeacon not supported; fallback may fail in Safari');
}
};
onMounted(() => {
window.addEventListener('beforeunload', handleBeforeUnload);
});
onUnmounted(() => {
window.removeEventListener('beforeunload', handleBeforeUnload);
});
⚠️ 注意事项:
- sendBeacon() 仅支持 POST 请求,且请求体类型受限(不支持 application/json 直接发送对象,需转为 URLSearchParams 或 FormData);
- 后端需正确解析对应格式(如 Express 中使用 urlencoded() 中间件处理 URLSearchParams);
- 不要依赖响应结果(API 返回值不可读),它设计为“即发即弃”,适合日志、草稿、统计类场景;
- 避免在 beforeunload 中同时调用 fetch + sendBeacon,以免冗余或竞争;
- 若需更高可靠性(如关键表单提交),应在用户主动操作时(如失焦、防抖输入)提前保存,而非仅依赖卸载事件。
总结:beforeunload 不是可靠的“最后保存时机”,而 sendBeacon() 是目前唯一被主流浏览器(含 Safari)广泛支持的、符合规范的卸载前数据上报方案。将其与 Vue 的生命周期结合,即可在不牺牲体验的前提下,显著提升数据持久化的成功率。











