[译] 改进关闭页面时的同步 XMLHttpRequest 问题

2,243 阅读4分钟

by @zhangbao(zhangbao) #0114

原文链接:Improving page dismissal in synchronous XMLHttpRequest(), by Joe Medley

引言

使用 XMLHttpRequest 发送同步请求的方式已经计划从规范中删除,不再建议开发者使用。我们可以使用 navigator.sendBeacon() 方法替代。

// ❌ No...
window.addEventListener("unload", function logData() {
  var xhr = new XMLHttpRequest();
  xhr.open("POST", "/log", false); // third parameter of `false` means synchronous
  xhr.send(analyticsData); 
});

// ✅ Yes!
window.addEventListener("unload", function logData() {
  navigator.sendBeacon("/log", analyticsData);
});

正文

在用户将页面完全关闭前,有时需要向后台传输一些必要数据(比如:用户行为分析数据)。为了避免数据丢失,一些网站会使用同步的 XMLHttpRequest() 请求,直到数据完全传递到服务器,再关闭页面。这种处理方式,可能会导致诸如页面延迟几秒后再关闭的糟糕体验,其实我们有更好的解决这类问题的方式。

XMLHttpRequest 规范提到 同步 XMLHttpRequest 已经计划从 Web 平台删除,开发者禁止将 new XMLHttpRequest().open(method, url [, async = true [, username = null [, password = null]]]) 方法中 async 参数赋值为 false 使用。Chrome 80 中迈出了第一步,开始禁止在如下这些事件处理器中使用同步 XMLHttpRequest:beforeunloadunloadpagehidevisibilitychange。Webkit 最近也有一个实现此行为的提交

image.png

规范中禁止开发者使用同步 XMLHttpRequest 的说明

本文中,我们将从两个方面简要描述如果应对这次 API 改变:

  1. 那么需要时间更新站点代码的开发者可以如何做?
  2. 同步 XMLHttpRequest 的替代方案有哪些?

临时 opt-out

Chrome 并未简单粗暴的直接关闭了对同步 XMLHttpRequest 的支持,它还给我们提供一些可供使用的 opt-outs。对英特网上的网页,我们可以使用一个 Origin Trial。注册完成后,会在页面的请求头中添加特定于 Origin 的 Token(origin-specific token) 来标识并启用同步 XMLHttpRequest 功能。这个支持会在 Chrome 86 发布前不久结束,时间大约在 2020 年 10 月下旬。

image.png

同步 XMLHttpRequest 的 Trial 支持会在 2020 年 10 月 21 日结束

可选方案

除非逼不得已,不要等到页面卸载的时候再发送数据给服务器。除了造成糟糕的用户体验之外,如果出现问题,还会有丢失数据的风险。卸载事件通常不会在移动端浏览器中触发,这是因为在移动设备中,有很多种在不引起触 unload 事件的情况下,退出页面或浏览器的方式。发送 XMLHttpRequest 的时候,我们可以选择使用少量请求数据(small payloads)的办法。现在,这是一个必要条件。根据规范要求,下面两种替代方案的上传数据的限制都是都是 64 KB。

Fetch keepalive

Fetch API  提供了一套健壮的与服务器端交互的方式,提供了跨越不同平台 API 的一致接口。它提供了一个选项 keepalive,保证不管发送请求的页面关闭与否,请求都会持续直到结束。

window.addEventListener('unload', {
  fetch('/siteAnalytics', {
    method: 'POST',
    body: getStatistics(),
    keepalive: true
  });
}

fetch() 方法的优点是可以更好地控制发送到服务器的内容。fetch() 方法会返回一个 Promise 对象,resolve 值是 Response 对象。 本例中,我没有使用 fetch() 返回值做任何其他的事情,是因为这个请求发生在页面卸载之时,因此并不一定会保证执行。

SendBeacon()

SendBeacon() 方法底层的使用的是 Fetch API。这样就能明白为什么它也有 64 KB  的上传数据限制,也能明白为什么它还能在页面卸载后继续请求。它的主要优点是简单,只要用一行代码就能搞定。

window.addEventListener('unload', {
  navigator.sendBeacon('/siteAnalytics', getStatistics());
}

总结

随着 Fetch API 在各浏览器开始广泛支持,XMLHttpRequest 有望在未来的某个时候从 Web 平台上移除。浏览器厂商都同意应该删除它,但这需要些时间。摒弃其中一个最糟糕的用例(也就说本文中介绍的——同步 XMLHttpRequest)是改善每个用户美好体验的第一步。

(正文完)


广告时间(长期有效)

我有一位好朋友开了一间猫舍,在此帮她宣传一下。现在猫舍里养的都是布偶猫。如果你也是个猫奴并且有需要的话,不妨扫一扫她的【闲鱼】二维码。不买也不要紧,看看也行。

(完)