如何在页面关闭时发送API请求以及手动实现ajax方法

3893次阅读 227人点赞 作者: WuBin 发布时间: 2023-07-25 09:57:55
扫码到手机查看

钩子函数

在一些需求背景下,我们需要在页面销毁(关闭/刷新)时将数据同步给后台,比如记录视频播放进度、页面浏览时长埋点等

window全局对象上,提供了beforeunload事件,会在浏览器窗口关闭或刷新时触发。

要实现这个需求,普遍的做法是在window.onbeforeunload监听事件回调中发起api请求。

const onBeforeunload = async () => {
  // 发起请求
}
window.addEventListener('beforeunload', onBeforeunload);
注意:在移动设备下,一些浏览器并不支持beforeunload事件,最可靠的方式是在visibilitychange事件中处理。
document.addEventListener('visibilitychange', function logData() {
  if (document.visibilityState === 'hidden') {
    ...
  }
});

发起请求的方式有如下几种:

  1. ajax(XMLHttpRequest)
  2. sendBeacon(Navigator.sendBeacon)
  3. fetch(Fetch keepalive)

下面,我们分析对比以上几种方式的优劣及适用性。

ajax

早期前后端进行数据交互多数都采用XMLHttpRequest方式创建一个HTTP请求,默认采用异步方式发起请求:

原生实现一个ajax方法:

const ajax = (config) => {
  const options = Object.assign({
    url: '',
    method: 'GET',
    headers: {},
    success: function () { },
    error: function () { },
    data: null,
    timeout: 0,
    async: true, // 是否异步发送请求,默认 true 是异步,同步需设置 false。
  }, config);
  const method = options.method.toUpperCase();

  // 1、创建 xhr 对象
  const xhr = new XMLHttpRequest();
  xhr.timeout = options.timeout; // 设置请求超时时间

  // 2、建立连接
  xhr.open(method, options.url, options.async); // 第三参数决定是以 异步/同步 方式发起 HTTP 请求

  // 设置请求头
  Object.keys(options.headers).forEach(key => {
    xhr.setRequestHeader(key, options.headers[key]);
  });

  // 3. 发送数据
  xhr.send(['POST', 'PUT'].indexOf(method) > -1 ? JSON.stringify(options.data) : null);

  // 4. 接收数据
  xhr.onreadystatechange = function () { // 处理响应
    if (xhr.readyState === 4) {
      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
        options.success(xhr.responseText);
      } else {
        options.error(xhr.status, xhr.statusText);
      }
    }
  };

  // 超时处理
  xhr.ontimeout = function () { options.error(0, 'timeout') };
  // 错误处理
  xhr.onerror = function () { options.error(0, 'error') };
  // xhr.abort(); // 取消请求  
}

对于 ajax 发起异步请求,若在发送过程中刷新或关闭浏览器,请求会被自动终止。

如果想在控制台查看刷新前页面接口调用情况,可勾选Preserve log选项,Network 会保留上个页面的请求记录。

可见,异步方式的 ajax 请求被浏览器自动cancel取消,无法将数据正常推送到后台。

一种处理方式是改为同步 ajax请求方式,在调用open建立连接时,第三参数async传递false表示以同步方式发送请求:

xhr.open(method, options.url, false);

但目前,谷歌浏览器已经不允许在页面关闭期间发起同步 XHR 请求,建议使用sendBeacon或者fetch keep-alive。我们接着往下看。

sendBeacon

navigator.sendBeacon()方法可用于通过HTTP POST将少量数据异步传输到 Web 服务器。

官方链接:developer.mozilla.org/zh-CN/docs/…

navigator.sendBeacon(url);
navigator.sendBeacon(url, data);
  • url: 指定将要被发送到的网络地址;
  • data: 可选,是将要发送的 ArrayBufferArrayBufferViewBlobDOMStringFormData 或 URLSearchParams 类型的数据。
  • return: 返回值。当用户代理成功把数据加入传输队列时,sendBeacon() 方法将会返回 true,否则返回 false
  • navigator.sendBeacon使用示例如下:

    // 通过 Blob 方式传递 JSON 数据
    const blob = new Blob(
      [JSON.stringify({ ... })], 
      { type: 'application/json; charset=UTF-8' }
    );
    // 发送请求
    navigator.sendBeacon(url, blob);
    

    sendBeacon发送请求有以下几个特点:

    1. 通过HTTP POST请求方式异步发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能;
    2. 支持跨域,但不支持自定义headers请求头,这意味着:如果用户信息Access-Token是作为请求头信息传递,需要后台接口支持url querystring参数传递解析。
    3. 考虑其兼容性。

    fetch keep-alive

    当使用fetch()请求时,如果把RequestInit.keeplive设置为true,即便页面被终止请求也会保持连接。

    fetch(url, {
      method: 'POST',
      body: JSON.stringify({ ... }),
      headers: {
        'Content-Type': 'application/json', // 指定 type
      },
      keepalive: true,
    });
    

    推荐使用FetchAPI 实现「离开网页时,将数据保存到我们的服务器上」。

    但它也有一些限制需要注意:

    1. 传输数据大小限制:无法发送兆字节的数数据,我们可以并行执行多个 keepalive 请求,但它们的 body 长度之和不得超过64KB
    2. 无法处理服务器响应:在网页文档卸载后,尽管设置keepalive 的 fetch 请求可以成功,但后续的响应处理无法工作。所以在大多数情况下,例如发送统计信息,这不是问题,因为服务器只接收数据,并通常向此类请求发送空的响应。

    思考:

    在框架的生命周期,如React useEffect可以实现页面关闭时发送 HTTP 请求记录数据吗?

    答案是:不可以

    尽管,我们所理解的useEffect中的销毁函数会在页面销毁时触发,但有一个前提条件是:程序保活正常运行,即ReactDOM.render 创建的 FiberRoot 正常运转

    试想,浏览器页面进行刷新或关闭,React 所启动的应用会直接中断停止,程序中页面定义的useEffect将不会被执行。

    相关资料

    点赞 支持一下 觉得不错?客官您就稍微鼓励一下吧!
    关键词:ajax,sendBeacon,fetch
    推荐阅读
    • uniapp实现被浏览器唤起的功能

      当用户打开h5链接时候,点击打开app若用户在已经安装过app的情况下直接打开app,若未安装过跳到应用市场下载安装这个功能在实现上主要分为两种场景,从普通浏览器唤醒以及从微信唤醒。

      9094次阅读 588人点赞 发布时间: 2022-12-14 16:34:53 立即查看
    • Vue

      盘点Vue2和Vue3的10种组件通信方式

      Vue中组件通信方式有很多,其中Vue2和Vue3实现起来也会有很多差异;本文将通过选项式API组合式API以及setup三种不同实现方式全面介绍Vue2和Vue3的组件通信方式。

      3845次阅读 287人点赞 发布时间: 2022-08-19 09:40:16 立即查看
    • JS

      几个高级前端常用的API

      推荐4个前端开发中常用的高端API,分别是MutationObserver、IntersectionObserver、getComputedstyle、getBoundingClientRect、requ...

      14071次阅读 914人点赞 发布时间: 2021-11-11 09:39:54 立即查看
    • PHP

      【正则】一些常用的正则表达式总结

      在日常开发中,正则表达式是非常有用的,正则表达式在每个语言中都是可以使用的,他就跟JSON一样,是通用的。了解一些常用的正则表达式,能大大提高你的工作效率。

      12908次阅读 442人点赞 发布时间: 2021-10-09 15:58:58 立即查看
    • 【中文】免费可商用字体下载与考证

      65款免费、可商用、无任何限制中文字体打包下载,这些字体都是经过长期验证,经得住市场考验的,让您规避被无良厂商起诉的风险。

      11469次阅读 920人点赞 发布时间: 2021-07-05 15:28:45 立即查看
    • Vue

      Vue3开发一个v-loading的自定义指令

      在vue3中实现一个自定义的指令,有助于我们简化开发,简化复用,通过一个指令的调用即可实现一些可高度复用的交互。

      15588次阅读 1244人点赞 发布时间: 2021-07-02 15:58:35 立即查看
    • JS

      关于手机上滚动穿透问题的解决

      当页面出现浮层的时候,滑动浮层的内容,正常情况下预期应该是浮层下边的内容不会滚动;然而事实并非如此。在PC上使用css即可解决,但是在手机端,情况就变的比较复杂,就需要禁止触摸事件才可以。

      14795次阅读 1205人点赞 发布时间: 2021-05-31 09:25:50 立即查看
    • Vue

      Vue+html2canvas截图空白的问题

      在使用vue做信网单页专题时,有海报生成的功能,这里推荐2个插件:一个是html2canvas,构造好DOM然后转canvas进行截图;另外使用vue-canvas-poster(这个截止到2021年3月...

      28950次阅读 2273人点赞 发布时间: 2021-03-02 09:04:51 立即查看
    • Vue

      vue-router4过度动画无效解决方案

      在初次使用vue3+vue-router4时候,先后遇到了过度动画transition进入和退出分别无效的情况,搜遍百度没没找到合适解决方法,包括vue-route4有一些API都进行了变化,以前的一些操...

      24982次阅读 1925人点赞 发布时间: 2021-02-23 13:37:20 立即查看
    交流 收藏 目录