业务场景:当用户关闭浏览器、跳转到其他浏览器窗口或刷新浏览器窗口时,需要向服务器发送一些统计数据或者发送指定指令接口去断流/断合流操作,此时就需要使用该用例中的方法了;
具体方案
常规方案
直接发送xhr请求
利用浏览器提供的
unload
或者beforeunload
事件,在事件回调中
进行xhr发送异步请求,但是存在的问题是会发送取消或发送失败。
- 通过将xhr异步请求改成同步发送解决上述问题
-
具体方法
- 利用
XMLHttpRequest
的第三个参数设置为false进行更改 - 通过设置
xhr.withCredentials=true
来实现在跨域时在请求中继续携带cookie数据,但是当设置后需要在后端的response头信息中添加Access-Control-Allow-Origin
,且必须指定域名且不能为*
Access-Control-Allow-Credentials
表示是否可以将请求的相应暴露给页面,返回true则表示可以暴露
jsconst 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
浏览器提供了
Navigator.sendBeacon
方法,用于将少量的数据通过post发送到服务器,发出的是异步请求,但是请求是作为浏览器任务执行的,与当前页面是脱钩的,因此此方法不会阻塞页面卸载流程和延迟后面页面的加载;
- 基本语法
-
Navigator.sendBeacon(url,data)
-
data可以是ArrayBufferView、Blob、DOMString或Formdate类型,但是需要保证
Content-Type
为以下三种之一application/x-www-form-urlencoded
jsconst 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"
- 当数据使用URLSearchParams 对象,content-type会被自动设置成
jsconst params = new URLSearchParams({ room_id: 333 }) navigator.sendBeacon("/live-server/join_room", params);
-
一般会用到
DOMString
、Blob
和Formdate
这三种对象作为数据发送到后端
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>