你可能不知道的浏览器 Beacon API

「这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战

埋点监控是前端常见的需求,通常我们直接发请求上报就可以了,但是有一些场景下会存在问题。如果用户上报没完成就关掉页面了,或者有些场景我们想要在用户离开页面时上报一些信息,此时情况就有所不同了。

在关闭页面之前执行动作,我们很容易想到这个很常见的弹窗:

image.png

查询相关文档可以知道这个是使用 onbeforeunload 事件来实现的:

The onbeforeunload property of the WindowEventHandlers mixin is the event handler for processing beforeunload events. These events fire when a window is about to unload its resources. At this point, the document is still visible and the event is still cancelable.

使用起来很简单:

window.addEventListener("beforeunload", function (e) {
	// Cancel the event
	e.preventDefault(); // If you prevent default behavior in Mozilla Firefox prompt will always be shown
	// Chrome requires returnValue to be set
	e.returnValue = "";
});
复制代码

这里使用 preventDefault 阻止页面关闭,部分浏览器还需要设置 returnValue。

beforeunload 事件触发之后可以选择不关闭,我们需要监听真正执行关闭的事件,根据名字 beforeunload,我们很容易想到是不是还有 unload 事件?developer.mozilla.org/en-US/docs/…,于是我们可以写出下面的代码:

window.addEventListener("beforeunload", function (e) {
  var client = new XMLHttpRequest();
  client.open("POST", "/log");
  client.send(data);
});
复制代码

但是会发现我们根本没有收到消息,因为我们发的是异步的请求,请请求发出去之前当前页面的上下文环境已经被销毁了,因此什么也发不出去。

按照这个思路是不是把异步改成同步就可以了,我们修改发送请求的代码:

window.addEventListener("unload", function (e) {
  var client = new XMLHttpRequest();
  client.open("POST", "/log", false);
  client.send(data);
});
复制代码

看起来功能实现了,不过现在有一个问题,我们为什么平时不实用同步请求,因为 js 是单线程的主线程阻塞用户侧会卡住,卡住的时间和网络状况有关,可能会较长。那么这个场景是否会存在卡住的问题呢?肯定是会的,用户想关闭或刷新页面,这时我们在等待请求返回,出现的现象就好像是卡死了关不掉,体验很不好。

我们需要一个 API 能保证在页面销毁之前发出请求,还不能阻塞页面,为了处理这个场景的问题,浏览器推出了 Beacon API。

sendBeacon 方法位于 navigator 上,可以在浏览器中使用,调用 sendBeacon 会发送一个异步的 post 请求,这个请求可以保证在页面完成卸载前发送出去,且不会阻塞页面卸载过程,调用起来就很简单:

window.addEventListener("unload", function (e) {
    navigator.sendBeacon("/log", data);
});
复制代码

这样请求就可以成功发送出去了。

Beacon API 被设计出来就是用于发送分析统计数据的,不建议用做其他用途。

sendBeacon 可以发送 ArrayBufferViewBlobDOMStringFormData 多种数据类型。

sendBeacon 的返回值为布尔类型,表示浏览器是否成功把请求加入发送队列。

Beacon API 不能在 web worker 中使用。

然而就在写这篇文章的时候,我在 MDN 上看到了这样的提示:

Warning: The onunload (and the unload event itself) are not the right features to use with sendBeacon(). Instead for sendBeacon(), use the visibilitychange and pagehide events. See discussion comments in the blog post Beacon API is broken.

volument.com/blog/sendbe…

看起来还是最经典的图片发送方式最可靠。。。:

function sendSomething(data, done, fail) {
  var img = new Image()
  img.onload = done
  img.onerror = fail
  img.src = '<https://tracking.volument.com>?' + $.param(data)
}
复制代码

参考:

developer.mozilla.org/en-US/docs/…

Guess you like

Origin juejin.im/post/7033803885093322783