当用户关闭标签页、提交表单或点击外链跳转时,你往往需要发送最后一条数据——会话时长、最后一次滚动位置、未捕获的错误报告、A/B测试的完成事件。第一反应是在beforeunload事件处理器里调用fetch()。但这里有一个隐蔽的陷阱:现代浏览器会在页面开始卸载的瞬间取消所有进行中的请求。你的数据永远不会到达服务器,而你永远无从知晓。 ## 为什么beforeunload里的fetch会失败 浏览器在页面卸载时的核心任务只有一个:以最快的速度导航离开。让页面保持存活以等待网络响应,与这一目标直接冲突。Chrome、Firefox、Safari等现代浏览器都会取消卸载期间仍在进行中的异步请求。beforeunload处理器执行了,fetch()被调用了,但请求在到达服务器之前就被静默终止了。 ```javascript // 这段代码看起来正确,但请求经常被取消 window.addEventListener('beforeunload', () => { fetch('/api/session-end', { method: 'POST', body: JSON.stringify({ duration: getSessionDuration() }), }); // 浏览器开始导航离开。fetch被取消。数据丢失。 }); ``` ## 旧的解决方案:同步XHR为何被抛弃 过去有一种变通方案:使用同步XMLHttpRequest,它会在请求完成前阻塞页面关闭。这个方案确实有效——但同时让每次关闭标签页都变得卡顿迟钝。浏览器在unload上下文中弃用了同步XHR,因为它必然损害用户体验。Chrome自2019年起就开始对这一用法输出警告。 ## 正确的API:navigator.sendBeacon navigator.sendBeacon()正是为这种场景而生的: ```javascript window.addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') { navigator.sendBeacon('/api/session-end', JSON.stringify({ duration: getSessionDuration(), page: location.pathname, })); } }); ``` 浏览器会将请求加入队列,即使页面已被销毁,也会异步完成投递。无论用户关闭标签页、浏览器将标签页转入后台挂起,还是用户跳转到其他页面——beacon依然会被送达。你无需关心响应内容,数据已经安全到达服务器。这正是sendBeacon设计的初衷:牺牲响应读取能力,换来确定性的投递保证。 ## 一个关键的坑:什么是"可被投递"的数据 使用sendBeacon时需要注意一个限制:如果你要发送的是Blob,请务必手动设置type属性。否则,Blob会默认以application/octet-stream类型发送,你的后端可能无法正确解析。更稳妥的做法是直接发送字符串、URLSearchParams或Blob(含显式type)。 ```javascript // 一个常见的错误:Blob未指定type const blob = new Blob([JSON.stringify(data)]); // 默认 application/octet-stream // 正确的做法 const blob = new Blob([JSON.stringify(data)], { type: 'application/json' }); navigator.sendBeacon('/api/session-end', blob); ``` ## 还有一个替代选择:keepalive fetch()也支持一个名为keepalive的选项,它能在页面卸载期间保持请求存活。与sendBeacon不同,你可以读到响应。但有一个硬性限制:请求体总大小不得超过64KB。如果你的心跳数据只是几条关键字段,keepalive完全够用;如果是要上报整个页面状态或日志快照,sendBeacon会更加合适。 ## 什么时候用哪个 | 场景 | 推荐方案 | |------|----------| | 只需上报数据、不关心响应 | sendBeacon | | 需要确认服务器是否收到、数据量小(<64KB) | fetch + keepalive | | 数据量较大(>64KB) | 在页面隐藏前提前发送,用普通fetch并做好失败重试 | 页面的最后一条数据,往往是最难到达的一条。使用sendBeacon,让这3秒不再成为数据黑洞。当用户按下关闭的那一刻,就是考验你上报能力的时候——是眼睁睁看着数据被浏览器吞噬,还是让它安全抵达服务器,选择权在你手中。
![]()
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.