2014 年,W3C 公布了信标(Beacon)的规范草案最终征求意见稿(目前曾经是候选举荐草案)。该标准定义了一个异步非阻塞的数据上报接口,能够最大限度地缩小对其余要害操作的资源占用,同时保障申请能失常收回。同年,该接口就被引入了 Firefox 和 Chrome,即 navigator.sendBeacon(下文简称为 sendBeacon)。
在理论开发工作中,该接口最常见的应用场景就是数据 埋点。与其余埋点技术计划相比,sendBeacon 的劣势在于:
- 不会跟业务代码抢占资源,而是在浏览器闲暇的时候再去发送;
- 在页面卸载(敞开、刷新、跳转)时也能保障申请发送,同时不阻塞页面卸载。
第一个配角——sendBeacon
sendBeacon 的办法原型非常简单:
navigator.sendBeacon(url, data);
其中:
- data 是将要发送的数据,能够是 ArrayBuffer、ArrayBufferView、Blob、FormData、URLSearchParams 或字符串。
- URL 是 data 将要被发送到的网络地址。
当数据胜利退出到传输队列时,返回值为 true,否则为 false。
一个简略的调用示例如下:
const data = new FormData();
data.append('id', '1');
data.append('type', 'test');
const result = navigator.sendBeacon(url, data);
console.log(result);
思考到 sendBeacon 可能会存在退出队列失败的状况,以及浏览器兼容性问题,一般来说还须要加上降级反对。
let result;
if (typeof navigator.sendBeacon === 'function') {result = navigator.sendBeacon(url, data);
}
if (!result) {const xhr = new XMLHttpRequest();
xhr.open('post', url);
xhr.send(data);
}
除此以外,sendBeacon 还有以下留神要点:
- 该办法的次要应用场景是将 大量 剖析数据发送给服务器,以确保申请可能疾速及时地实现,因而它会限度最大的负载体积。
- 该办法总是以 HTTP POST 去发送申请,且无奈设置自定义申请头或其余与申请、响应相干的属性。
- 该办法没有提供获取响应后果的形式。
- 调用该办法时必须以 navigator 作为上下文对象,否则会抛出 Illegal invocation 的异样。
第二个配角——Blob
如果须要通过 sendBeacon 发送 JSON 数据,能够这样调用:
navigator.sendBeacon(
url,
new Blob([JSON.stringify(data)], {type: 'application/json'})
);
发送这个申请时,浏览器会把 content-type 申请头设为 application/json。
Chrome 晚期版本的平安问题
上述调用形式存在一个历史问题:Chrome 晚期反对的 sendBeacon(Chrome 39~58)存在平安危险,跨域申请不会进行预检,相当于能够跨域提交任何数据。于是,从 Chrome 59 开始,对于跨域申请,浏览器不容许设置 content-type 申请头为 application/x-www-form-urlencoded、multipart/form-data 或者 text/plain 以外的值。一旦呈现了这种状况,sendBeacon 就会抛出异样。
这个问题直到 Chrome 81 才被解决,尔后由 sendBeacon 发送的跨域申请均遵循跨域安全策略,只有 content-type 的值不是上述的三个值之一,就要先进行预检,预检通过后能力发送数据。
思考到很多浏览器或者 app 都是以 Chrome 为内核,并且版本多种多样,所以如果有发送 JSON 的需要,能够将内容以 text/plain 的类型发送,再由后端把文本解析为 JSON 数据。
navigator.sendBeacon(
url,
new Blob([JSON.stringify(data)], {type: 'text/plain' // 不指定 type 或者指定为空字符串也是不行的})
);
如果必须以 application/json 上报数据,那就须要做降级反对,当 sendBeacon 抛出异样时降级到 XMLHttpRequest。
let result;
const data = new Blob([JSON.stringify(data)], {type: 'application/json'});
try {result = navigator.sendBeacon(url, data);
} catch (e) { }
if (!result) {const xhr = new XMLHttpRequest();
xhr.open('post', url);
xhr.send(data);
}
iOS 12 微信下阻塞同域名申请
如果通过 sendBeacon 发送 application/json 的 Blob,在 iOS 12.7(未测试 iOS 11 和 iOS 12 的其余版本)的微信下还有一个奇怪的问题。
如上方截图所示,vConsole 显示 sendBeacon 的申请(api.php)曾经收回,但后续几个同域名申请(get.php)都是 pending 状态,只有非同域申请(219878)能失常收回。如果用 Charles 或者 Fiddler 抓包,能够发现除 219878 外的所有申请其实都没有收回。微信社区也有相似问题的反馈。
这种状况在同版本零碎的 iOS Safari 下是不会呈现的,所以也不晓得是什么起因导致,把 Blob 的 type 改成 text/plain 之后也能够解决。
总结
综上所述,如果心愿通过 sendBeacon 来上报埋点,还是得尽量应用 application/x-www-form-urlencoded、multipart/form-data 或 text/plain 作为 content-type,防止应用 application/json。此外,还必须思考 sendBeacon 调用失败或入队失败时的保底计划。
本文同时发表于作者集体博客 https://mrluo.life/article/detail/149/send-blob-via-sendbeacon