Navigator.sendBeacon()

用户卸载网页的时候,有时需要向服务器发一些数据。很自然的做法是在unload事件或beforeunload事件的监听函数里面,使用XMLHttpRequest对象发送数据。但是,这样做不是很可靠,因为XMLHttpRequest对象是异步发送,很可能在它即将发送的时候,页面已经卸载了,从而导致发送取消或者发送失败。

解决方法就是unload事件里面,加一些很耗时的同步操作。这样就能留出足够的时间,保证异步 AJAX 能够发送成功。

  1. function log() {
  2. let xhr = new XMLHttpRequest();
  3. xhr.open('post', '/log', true);
  4. xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  5. xhr.send('foo=bar');
  6. }
  7. window.addEventListener('unload', function(event) {
  8. log();
  9. // a time-consuming operation
  10. for (let i = 1; i < 10000; i++) {
  11. for (let m = 1; m < 10000; m++) { continue; }
  12. }
  13. });

上面代码中,强制执行了一次双重循环,拖长了unload事件的执行时间,导致异步 AJAX 能够发送成功。

类似的还可以使用setTimeout。下面是追踪用户点击的例子。

  1. // HTML 代码如下
  2. // <a id="target" href="https://baidu.com">click</a>
  3. const clickTime = 350;
  4. const theLink = document.getElementById('target');
  5. function log() {
  6. let xhr = new XMLHttpRequest();
  7. xhr.open('post', '/log', true);
  8. xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  9. xhr.send('foo=bar');
  10. }
  11. theLink.addEventListener('click', function (event) {
  12. event.preventDefault();
  13. log();
  14. setTimeout(function () {
  15. window.location.href = theLink.getAttribute('href');
  16. }, clickTime);
  17. });

上面代码使用setTimeout,拖延了350毫秒,才让页面跳转,因此使得异步 AJAX 有时间发出。

这些做法的共同问题是,卸载的时间被硬生生拖长了,后面页面的加载被推迟了,用户体验不好。

为了解决这个问题,浏览器引入了Navigator.sendBeacon()方法。这个方法还是异步发出请求,但是请求与当前页面线程脱钩,作为浏览器进程的任务,因此可以保证会把数据发出去,不拖延卸载流程。

  1. window.addEventListener('unload', logData, false);
  2. function logData() {
  3. navigator.sendBeacon('/log', analyticsData);
  4. }

Navigator.sendBeacon方法接受两个参数,第一个参数是目标服务器的 URL,第二个参数是所要发送的数据(可选),可以是任意类型(字符串、表单对象、二进制对象等等)。

  1. navigator.sendBeacon(url, data)

这个方法的返回值是一个布尔值,成功发送数据为true,否则为false

该方法发送数据的 HTTP 方法是 POST,可以跨域,类似于表单提交数据。它不能指定回调函数。

下面是一个例子。

  1. // HTML 代码如下
  2. // <body onload="analytics('start')" onunload="analytics('end')">
  3. function analytics(state) {
  4. if (!navigator.sendBeacon) return;
  5. var URL = 'http://example.com/analytics';
  6. var data = 'state=' + state + '&location=' + window.location;
  7. navigator.sendBeacon(URL, data);
  8. }