sendBeacon--实现浏览器关闭时上报数据的可靠利器
Navigator.sendBeacon()
是一个用于异步发送小型数据包的方法,它允许避免延迟敏感数据的丢失。
简介
这个方法主要用于满足统计和诊断代码的需要,这些代码通常尝试在卸载(unload
)文档之前向 Web 服务器发送数据。过早的发送数据可能导致错过收集数据的机会。然而,对于开发者来说保证在文档卸载期间发送数据一直是一个困难。因为用户代理通常会忽略在 unload
事件处理器中产生的异步 XMLHttpRequest
。
过去,为了解决这个问题,统计和诊断代码通常要在
- 发起一个同步
XMLHttpRequest
来发送数据。 - 创建一个
<img>
元素并设置src
,大部分用户代理会延迟卸载(unload
)文档以加载图像。 - 创建一个几秒的
no-op
循环。
上述的所有方法都会迫使用户代理延迟卸载文档,并使得下一个导航出现的更晚。下一个页面对于这种较差的载入表现无能为力。
而使用 sendBeacon()
方法会使用户代理在有机会时异步地向服务器发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能,这意味着:
- 数据发送是可靠的。
- 数据异步传输。
- 不影响下一导航的载入。
数据是通过 HTTP POST 请求发送的。
语法:
1 | navigator.sendBeacon(url, data) |
- url: 要发送数据包的 URL
- data: 要发送的数据,可以是
ArrayBuffer
、ArrayBufferView
、Blob
、DOMString
、FormData
或URLSearchParams
类型的数据。
返回值
sendBeacon()
方法返回一个布尔值:
- true: 数据被异步缓存并将在页面卸载后发送。但这并不保证数据已被成功发送或接收。
- false: 数据无法被缓存,通常是因为数据太大或 URL 无效。
所以我们不能依靠返回值来判断数据是否已被成功发送,它仅表示数据是否已进入发送队列。
使用场景
sendBeacon()
方法适用于以下场景:
- 日志和分析数据的收集
- 错误和性能报告
- 用户活动和交互跟踪
例如,我们可以在用户即将离开页面前使用 sendBeacon()
发送一些日志或分析数据,以避免这些数据的丢失。
浏览器支持
sendBeacon()
方法被广泛支持,可以在不考虑 polyfill 的情况下使用:
- Chrome 42+
- Firefox 42+
- IE 10+
- Edge 12+
- Safari 6+
- Opera 29+
- Android Browser 53+
- iOS Safari 6+
原理
sendBeacon()
方法会创建一个图像对象(图片像素),该对象加载一个包含数据的 URL。这会触发 HTTP 请求并发送数据,但由于它是一个图像,不会延迟页面的 unload 和关闭事件。
虽然 sendBeacon()
是基于图像像素的方式实现的,但它与传统的像素跟踪不同,仅用于发送数据而不收集任何信息。
数据上报的时机
在会话结束时发送统计数据
网站通常希望在用户完成页面浏览后向服务器发送分析或诊断数据,最可靠的方法是在 visibilitychange
事件发生时发送数据:
1 | document.addEventListener('visibilitychange', function logData() { |
使用 pagehide
作为回退
可使用 pagehide
事件来代替部分浏览器未实现的 visibilitychange
事件。和 beforeunload
与 unload
事件类似,这一事件不会被可靠地触发(特别是在移动设备上),但它与 bfcache
兼容。
避免使用 unload 和 beforeunload
过去,许多网站使用 unload
或 beforeunload
事件以在会话结束时发送统计数据。然而这是不可靠的,在许多情况下(尤其是移动设备)浏览器不会产生 unload
、beforeunload
或 pagehide
事件。下面列出了一种不触发上述事件的情况:
- 用户加载了网页并与其交互。
- 完成浏览后,用户切换到了其他应用程序,而不是关闭选项卡。
- 随后,用户通过手机的应用管理器关闭了浏览器应用。
此外,unload
事件与现代浏览器实现的往返缓存(bfcache
)不兼容。在部分浏览器(如:Firefox)通过在 bfcache
中排除包含 unload
事件处理器的页面来解决不兼容问题,但这存在性能损失。其他浏览器,例如 Safari 和 Android 上的 Chrome 浏览器则采取用户在同一标签页下导航至其他页面时不触发 unload
事件的方法来解决不兼容问题。
Firefox 也会在 bfcache
中排除包含 beforeunload
事件处理器的页面。
数据限制
sendBeacon()
方法适用于发送小段文本数据,理论上可以发送任意 MIME
类型,但数据大小限制在 64 kB 以下。如果超过这个限制,数据将无法被缓存并立即触发 “failed to queue data for sending” 错误。
虽然不同浏览器的具体限制各不相同,但为确保最大兼容性,我们应将 sendBeacon()
发送的数据控制在这个限制之内。
安全性
与常规 XHR
请求不同,sendBeacon()
发送的数据不受 CORS
规则的限制,这意味着我们可以将数据发送到任意 URL
,甚至是第三方域。
所以,使用 sendBeacon()
方法前,我们必须确保目标 URL
是可信的,不会向其发送敏感数据,避免信息泄露的风险。
示例
一个完整的 sendBeacon()
使用示例:
1 | let logData = { /* object containing info to log */ }; |
这里我们:
- 检查要发送的数据大小是否在 64 kB 以内
- 如果是,使用
sendBeacon()
方法异步发送数据 - 根据返回值判断数据是否已进入发送队列
- 如果超过大小限制,在控制台打印错误
- 以避免数据发送失败导致的问题