• Navigator.sendBeacon 无阻塞发送统计数据


    业务场景
    当用户关闭浏览器、刷新浏览器或者跳转其他页面时,向服务器发送一些统计数据。

    常规方案
    1. 直接发送 xhr 请求
    我们会优先想到监听页面的unload或者beforeunload事件,在事件回调里使用XMLHttpRequest发送异步请求。

    但是由于是xhr请求是异步发送,很可能在它即将发送的时候,页面已经卸载了,从而导致发送取消或者发送失败。异步请求响应返回后,由于页面和相关资源已经卸载,会引起function not found的错误。


    解决方法就是 AJAX 通信改成同步发送,即只有发送完成,页面才能卸载。

    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));
    };

    将xhr请求改为同步,虽然能够完成发送数据,但存在以下两个问题:

    部分浏览器已经不支持同步的 XMLHttpRequest 对象了(即open()方法的第三个参数为false);
    xhr请求改为同步后,会阻塞页面的卸载和跳转,导致下一个页面导航加载的时机变晚,用户体验较差。
    2. 动态图片
    通过在unload事件处理器中,创建一个图片元素并设置它的 src 属性的方法来延迟卸载以保证数据的发送。因为绝大多数浏览器会延迟卸载以保证图片的载入,所以数据可以在卸载事件中发送。

    const reportData = (url, data) => {
    let img = document.createElement('img');
    const params = [];
    Object.keys(data).forEach((key) => {
    params.push(`${key}=${encodeURIComponent(data[key])}`);
    });
    img.onload = () => img = null;
    img.src = `${url}?${params.join('&')}`;
    };


    这种方法存在同样的问题,页面卸载流程被阻塞,后面页面的加载时机被延迟,用户体验不好

    Navigator.sendBeacon
    浏览器引入的sendBeacon方法,**发出的是异步请求,但是请求是作为浏览器任务执行的,与当前页面是脱钩的。**因此该方法不会阻塞页面卸载流程和延迟后面页面的加载。
    1. 基本用法

    navigator.sendBeacon(url, data);

    url 就是上报地址,data 可以是 ArrayBufferView,Blob,DOMString 或 Formdata,根据官方规范,需要 request header 为 CORS-safelisted-request-header,在这里则需要保证 Content-Type 为以下三种之一:

    application/x-www-form-urlencoded
    multipart/form-data
    text/plain
    我们一般会用到 DOMString , Blob 和 Formdata 这三种对象作为数据发送到后端,下面以这三种方式为例进行说明。

    // 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;如果受到队列总数、数据大小的限制后,会返回false。返回ture后,只是表示进入了发送队列,浏览器会尽力保证发送成功,但是否成功了,无法判断。

    2. 发送数据大小限制
    目前没有给出具体的发送数据大小限制标准,不过有人做了下面的测试,当数据长度是65536时,异步请求进入浏览器发送队列失败,表明数据大小是有限制,不同的浏览器应该有所差别。:

    var url = 'http://jsfiddle.net?sendbeacon';
    var n = 65536; // sendBeacon limit for Chrome v40 on Windows (2^16)
    var data = new Array(n+1).join('X'); // generate string of length n
    
    if(!navigator.sendBeacon(url, data))
    {
    alert('data limit reached');
    }

    3. 兼容性
    sendBeacon方法存在兼容性问题,除了IE,大部分浏览器都已经支持,兼容情况如下图所示:


    考虑到兼容性问题,使用过程中,我们可以采用下面两种方法做兼容:

    不支持时,用同步的xhr替代,也就是上面提到的第一种方法。
    navigator.sendBeacon || new Function('var xhr=new XMLHttpRequest();xhr.open("POST",arguments[0],true);r.send(arguments[1]);');
    1
    补丁文件

    // npm 包
    npm install navigator.sendbeacon
    
    // 外链
    <script src="https://unpkg.com/navigator.sendbeacon"></script>
    <!-- OR -->
    <script src="https://cdn.jsdelivr.net/npm/navigator.sendbeacon"></script>

    总结
    sendBeacon方法具有如下特点:

    发出的是异步请求,并且是POST请求,后端解析参数时,需要注意处理方式;
    发出的请求,是放到的浏览器任务队列执行的,脱离了当前页面,所以不会阻塞当前页面的卸载和后面页面的加载过程,用户体验较好;
    只能判断出是否放入浏览器任务队列,不能判断是否发送成功;
    Beacon API不提供相应的回调,因此后端返回最好省略response body。
    ————————————————
    版权声明:本文为CSDN博主「青天诀」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/u012193330/article/details/102778979

  • 相关阅读:
    flex产生水平滚动条
    js中的类
    typescript
    vue练习
    vue-cli2脚手架搭建
    Luogu P1970 花匠
    Luogu P1311 选择客栈
    Luogu P1016 旅行家的预算
    Luogu P1144 最短路计数
    Luogu P1091 合唱队形
  • 原文地址:https://www.cnblogs.com/cangqinglang/p/12403105.html
Copyright © 2020-2023  润新知