关闭/刷新浏览器时继续发送请求的方法

业务场景:当用户关闭浏览器、跳转到其他浏览器窗口或刷新浏览器窗口时,需要向服务器发送一些统计数据或者发送指定指令接口去断流/断合流操作,此时就需要使用该用例中的方法了;

具体方案

常规方案

直接发送xhr请求

利用浏览器提供的unload或者beforeunload事件,在事件回调中进行xhr发送异步请求,但是存在的问题是会发送取消或发送失败。

  • 通过将xhr异步请求改成同步发送解决上述问题
    • 具体方法

      • 利用XMLHttpRequest的第三个参数设置为false进行更改
      • 通过设置xhr.withCredentials=true来实现在跨域时在请求中继续携带cookie数据,但是当设置后需要在后端的response头信息中添加Access-Control-Allow-Origin,且必须指定域名且不能为*
      • Access-Control-Allow-Credentials表示是否可以将请求的相应暴露给页面,返回true则表示可以暴露
      js 复制代码
      const syncReport = (url, { data = {}, headers = {} } = {}) => {
        const xhr = new XMLHttpRequest();
        xhr.open("POST", url, false);
        xhr.withCredentials = true;
        Object.keys(headers).forEach((key) => {
          xhr.setRequestHeader(key, headers[key]);
        });
        xhr.send(JSON.stringify(data));
      };
    • 存在问题

      • 部分浏览器已经不支持同步的XMLHttpRequest
      • 改为同步后,会阻塞页面的卸载和跳转,影响用户体验
fetch + keepalive实现异步请求升级

常规的异步请求是非阻塞的,异步操作有一个好处是不占用主进程,但是存在的问题也是明显的,如果主进程销毁了,那么原来的异步请求就会被忽略,具体展示就是Network中的请求被canceled

fetch是ES6新增的一个HTTP请求的方式,keepalive是fetch的一个属性,目的是告知浏览器即使页面卸载了,也要在后台保持连接,继续发送数据,用法比较简单,直接传入true即表示打开;

fetch发送FormData数据时,不需要在头部设置Content-Type:multipart/form-data,否则会出现数据信息错误的问题

js 复制代码
window.addEventListener('beforeunload', (e) => {
    console.error('mounted 的 beforeunload ====>', e)
    let formdata = new FormData();
    formdata.append("goods_id",this.goodsInfo.id)
    formdata.append("tid",this.AgoraRTCOptions.sn)
    fetch(`${location.protocol}/api/WebPushApi/stopRtcMergeTask`, {
        method: "POST",
        // headers: {
        //     'Content-Type':'application/x-www-form-urlencoded'
        // },
        body: formdata,
        // body: {
        //     goods_id: this.goodsInfo.id,
        //     tid: this.AgoraRTCOptions.sn
        // },
        keepalive: true
    });
    this.asyncStopMergeStream()
    
    this.socketInstance && this.socketInstance.emitQuitPush({
        room: this.goodsInfo.id
    })
    
})

浏览器提供了Navigator.sendBeacon方法,用于将少量的数据通过post发送到服务器,发出的是异步请求,但是请求是作为浏览器任务执行的,与当前页面是脱钩的,因此此方法不会阻塞页面卸载流程和延迟后面页面的加载;

  • 基本语法
    • Navigator.sendBeacon(url,data)

      • data可以是ArrayBufferView、Blob、DOMString或Formdate类型,但是需要保证Content-Type为以下三种之一

        • application/x-www-form-urlencoded
        js 复制代码
        const blob = new Blob([`room_id=333`], {type : 'application/x-www-form-urlencoded'});
        navigator.sendBeacon("/live-server/join_room", blob);
        • multipart/form-data
          • 使用FormData时,content-type会被自动设置成"multipart/form-data"
        js 复制代码
        // 此时该请求会自动设置请求头的 Content-Type 为 multipart/form-data。
        var data = {
          room_id: 222,
          user_id: 733,
        };
        const reportData = (url = "/live-server/join_room", data) => {
          const formData = new FormData();
          Object.keys(data).forEach((key) => {
            let value = data[key];
            if (typeof value !== "string") {
              // formData只能append string 或 Blob
              value = JSON.stringify(value);
            }
            formData.append(key, value);
          });
          navigator.sendBeacon(url, formData);
        };
        • text/plain
          • 当数据使用URLSearchParams 对象,content-type会被自动设置成"text/plain;charset=UTF-8"
        js 复制代码
        const params = new URLSearchParams({ room_id: 333 })
        navigator.sendBeacon("/live-server/join_room", params);
      • 一般会用到DOMStringBlobFormdate这三种对象作为数据发送到后端

      js 复制代码
      // 1. DOMString类型,该请求会自动设置请求头的 Content-Type 为 text/plain
      const reportData = (url, data) => {
        navigator.sendBeacon(url, data);
      };
      
      // 2. 如果用 Blob 发送数据,这时需要我们手动设置 Blob 的 MIME type,
      // 一般设置为 application/x-www-form-urlencoded。
      const reportData = (url, data) => {
        const blob = new Blob([
          JSON.stringify(data),
          {
            type: "application/x-www-form-urlencoded",
          },
        ]);
        navigator.sendBeacon(url, blob);
      };
      
      // 3. 发送的是Formdata类型,
      // 此时该请求会自动设置请求头的 Content-Type 为 multipart/form-data。
      var data = {
        name: "前端名狮子",
        age: 20,
      };
      const reportData = (url, data) => {
        const formData = new FormData();
        Object.keys(data).forEach((key) => {
          let value = data[key];
          if (typeof value !== "string") {
            // formData只能append string 或 Blob
            value = JSON.stringify(value);
          }
          formData.append(key, value);
        });
        navigator.sendBeacon(url, formData);
      };
    • sendBeacon如果成功进入到浏览器的发送队列后会返回true,但有时会收到队列总数、数据大小限制、CORB(跨域读取阻止)等限制返回false;同时返回true并不能判断是否发送成功,只能说浏览器会尽力保证发送成功

    • 其返回数据只是表示是否成功加入到浏览器的发送队列中,因此发送的接口其相应体可以忽略body等内容

    • 但是该API只支持post请求,发送的数据也比较少,兼容性除了IE都较好

    • 该方法也可以用来做前端埋点、监控用户活动等

ping方法

ping包含一个以空格分割的URL列表,传入的值为字符串,其定义在<a>标签上是指定用户遵循超链接时需要通知的URL列表,当用户在点击超链接时,ping属性将向指定的URL发送一个简单的HTTP post请求,此属性对于监视/跟踪很有用,其不支持IE/Safari;其指定的URL必须是一个由一个或多个有效URL组成的空格分割的列表;

<a href="/other.html" id="link" ping="http://localhost:8088/log">离开页面</a>

相关推荐
果子切克now25 分钟前
vue2与vue3知识点
前端·javascript·vue.js
积水成江1 小时前
Vite+Vue3+SpringBoot项目如何打包部署
java·前端·vue.js·windows·spring boot·后端·nginx
一丝晨光1 小时前
Web技术简史、前后端分离、游戏
前端·javascript·css·游戏·unity·前后端分离·cocos
假客套1 小时前
2024 uniapp入门教程 01:含有vue3基础 我的第一个uniapp页面
前端·uni-app·vue3·hbuilder x
柒小毓1 小时前
网站开发基础:HTML、CSS
前端·css·html
&白帝&3 小时前
Vue.js 过渡 & 动画
前端·javascript
总是学不会.3 小时前
SpringBoot项目:前后端打包与部署(使用 Maven)
java·服务器·前端·后端·maven
Fanfffff7204 小时前
深入探索Vue3组合式API
前端·javascript·vue.js
光影少年4 小时前
node配置swagger
前端·javascript·node.js·swagger
昱禹4 小时前
关于CSS Grid布局
前端·javascript·css