解决页面关闭时数据丢失:用好sendBeacon与fetch的keepalive

更新日期: 2025-09-10阅读: 27标签: fetch

不少开发者遇到过这样的问题:线上数据看板显示用户行为数据不全,明明功能已经上线,但收集到的数据却比预期少很多。经过排查发现,很多数据丢失发生在用户关闭页面或跳转离开的时候。原本通过fetch发送的请求,因为页面关闭而被浏览器强制取消,导致数据无法成功送达服务器。

问题并不在于代码逻辑,而是浏览器的运行机制决定的。当用户关闭标签页时,浏览器会立即停止当前页面的所有JavaScript执行,包括尚未完成的网络请求。这意味着,即使用户已经触发了数据上报,这些请求也可能在到达服务器前就被中断了。


navigator.sendBeacon的解决方案

从2014年开始,浏览器提供了一个专门的api来解决这个问题:navigator.sendBeacon()。这个API的设计目的就是在页面卸载时可靠地发送数据。

与fetch不同,sendBeacon不会等待服务器响应,也不会延迟页面的关闭过程。它会将请求放入浏览器的发送队列,然后在后台完成传输,即使JavaScript已经停止执行也不会影响发送。

对比一下两种写法的区别:

// 这种写法在页面关闭时可能失败
window.addEventListener('beforeunload', () => {
  fetch('/analytics', {
    method: 'POST',
    body: JSON.stringify({ event: 'page_exit' })
  }); // 可能被取消
});

// 使用sendBeacon更可靠
window.addEventListener('beforeunload', () => {
  navigator.sendBeacon('/analytics', 
    JSON.stringify({ event: 'page_exit' })
  ); // 能够成功进入发送队列
});

当用户快速关闭页面或跳转到其他页面时,这两种方式的差异非常明显。浏览器通常会取消fetch请求以确保页面快速关闭,而sendBeacon是专门为此场景设计的。


实际效果对比

实际测试数据显示,navigator.sendBeacon()在页面切换时的数据送达率达到95.8%,而传统的fetch()方法只有84.9%。在移动端,这个差距更加明显。由于移动设备上更激进的内存回收机制和应用后台运行限制,fetch的失败率更高,而sendBeacon则能保持较好的稳定性。

sendBeaconAPI特别适合处理小型但重要的数据上报:

  • 更高的可靠性:请求进入浏览器原生队列,能够跨越页面卸载的窗口

  • 不会阻塞页面:不会拖慢用户的导航体验

  • 使用简单:不需要复杂的备用方案或庞大的埋点库


使用限制和注意事项

sendBeacon有两个主要限制:单次请求大小不能超过64KB,并且多个beacon请求共享这个大小限制。

// 正好64KB,可以正常发送
navigator.sendBeacon('/log', 'A'.repeat(65536));

// 超过限制,会返回false表示失败
navigator.sendBeacon('/log', 'A'.repeat(65537));

sendBeacon()返回一个布尔值,表示请求是否成功进入队列。建议总是检查这个返回值并提供备用方案:

const success = navigator.sendBeacon('/analytics', data);
if (!success) {
  // 备用方案:使用fetch或拆分数据
  fetch('/analytics', { 
    method: 'POST', 
    body: data 
  });
}

另一个需要注意的限制是sendBeacon不支持自定义请求头。当发送字符串数据时,浏览器会使用默认的text/plain Content-Type,服务器端需要相应处理。

如果需要发送JSON数据,可以使用Blob对象,但这会触发CORS预检请求:

const data = { event: 'click', count: 42 };
const blob = new Blob([JSON.stringify(data)], { 
  type: 'application/json' 
});

navigator.sendBeacon('/analytics', blob);
// 注意:这会触发CORS预检请求

大多数团队选择在服务器端解析text/plain内容,以避免额外的预检请求开销。


更多应用场景

除了页面退出埋点,sendBeacon还非常适合实时错误上报:

window.addEventListener('error', (event) => {
  const errorData = {
    message: event.message,
    filename: event.filename,
    line: event.lineno,
    stack: event.error?.stack,
    timestamp: Date.now()
  };
  
  navigator.sendBeacon('/errors', JSON.stringify(errorData));
});

对于用户交互追踪,sendBeacon也是很好的选择:

document.addEventListener('click', (event) => {
  const clickData = {
    element: event.target.tagName,
    x: event.clientX,
    y: event.clientY,
    timestamp: Date.now()
  };
  
  navigator.sendBeacon('/interactions', JSON.stringify(clickData));
});


现代替代方案:fetch with keepalive

近年来,另一种解决方案变得越来越流行:使用fetch的keepalive选项。

fetch('/analytics', {
  method: 'POST',
  body: JSON.stringify(data),
  keepalive: true,
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token'
  }
});

这种方式既保持了Beacon在页面卸载期的可靠性,又保留了fetch的灵活性,包括自定义请求头、丰富的HTTP方法和对响应的处理能力。到2025年,主流浏览器都对keepalive提供了很好的支持。


如何选择

选择navigator.sendBeacon()当:

  • 需要轻量级的埋点或日志记录

  • 处理页面退出或会话结束时的数据

  • 进行错误上报和诊断

  • 追求最大兼容性和最简单实现

选择fetch({ keepalive: true })当:

  • 需要自定义请求头或认证信息

  • 需要处理服务器响应或复杂HTTP配置

  • 需要更通用的请求能力


浏览器兼容性

sendBeaconAPI在现代浏览器中已有很好的支持,覆盖约92%的用户:

  • Chrome 39+

  • Firefox 31+

  • Safari 11.1+

  • Edge 14+

Internet Explorer不支持此API,但在2025年这已经不再是主要问题。为了最大程度的兼容性,可以添加特性检测和备用方案:

if (navigator.sendBeacon) {
  navigator.sendBeacon('/log', data);
} else {
  // 回退到XMLHttpRequest
  const xhr = new XMLHttpRequest();
  xhr.open('POST', '/log', false);
  xhr.send(data);
}


总结

navigator.sendBeacon()和fetch with keepalive彻底解决了页面关闭时数据丢失的问题。无论是用户快速关闭页面还是跳转到其他网站,重要的数据都能可靠地送达服务器。对于需要准确收集用户行为数据的应用来说,正确使用这些API是确保数据完整性的关键。

本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!

链接: https://fly63.com/article/detial/12913

Fetch -- http请求的另一种姿势

传统Ajax是利用XMLHttpRequest(XHR)发送请求获取数据,不注重分离的原则。而Fetch API是基于Promise设计,专为解决XHR问题而出现。fetch API看起来简单,却是js语法不断增强提高带来的改善。

JavaScript中fetch接口的用法使用

fetch就是XMLHttpRequest的一种替代方案。如果有人问你,除了Ajax获取后台数据之外,还有没有其他的替代方案?这是你就可以回答,除了XMLHttpRequest对象来获取后台的数据之外,还可以使用一种更优的解决方案fetch。

当fetch遇到302状态码,会发生什么?

当fetch遇到302状态码,会发生什么?fetch不能拦截302,浏览器会自动从302响应的头信息的重定向地址中取到数据。针对认证的情况,后端可以返回401状态码,让前端去检查返回的状态码并据此执行相应操作。

Fetch使用

Fetch API 提供了一个获取资源的接口(包括跨域请求)。任何使用 过 XMLHttpRequest 的人都能轻松上手,但新的API提供了更强大和 灵活的功能集。Fetch 提供了对 Request 和 Response (以及其他与网络请求有关的)对象的通用定义。

fetch使用的常见问题及其解决办法

fetch默认不携带cookie配置其 credentials 项,其有3个值:omit: 默认值,忽略cookie的发送;same-origin: 表示cookie只能同域发送,不能跨域发送;include: cookie既可以同域发送,也可以跨域发送

fetch API

fetch:一个获取资源的接口,类似于ajax,是基于 Promise之上设计,旧版本IE 完全不支持,须借助 polyfill来兼容,提供了对 Request 和 Response 对象的通用定义

fetch使用,ajax替代方案

Fetch 提供了一个 JavaScript接口,用于访问和操纵HTTP管道的部分,例如请求和响应。它还提供了一个全局 fetch()方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。

TypeScript Fetch封装使用

Fetch API 提供了一个 JavaScript接口,用于访问和操纵HTTP管道的部分,例如请求和响应。它还提供了一个全局 fetch()方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源

通过fetch发送 post 请求下载文件

最近遇到一个下载的需求,由于 url 参数太长(常用的下载方法 a 标签或者 location.href 的方法都是 get 请求,get 请求参数长度有限制),无法下载,考虑了好几种方案,最终还是觉得通过 ajax 的 POST 方法进行下载,比较容易实现

Js中使用fetch进行异步请求

在 AJAX 时代,进行 API 等网络请求都是通过 XMLHttpRequest 或者封装后的框架进行网络请求。 现在产生的 fetch 框架简直就是为了提供更加强大、高效的网络请求而生,虽然在目前会有一点浏览器兼容的问题

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!