在web项目开发中,关于浏览器关闭事件有两个很常见的问题:为什么我没有监听浏览器关闭事件? 我监听到了这个事件,但写在事件里的异步请求为什么发送不成功?
原因分析:这两个问题无外乎两个原因:浏览器关闭事件未被触发 和 异步请求发送失败。
原因1:关闭浏览器时一定会触发事件吗?如果不一定,那什么条件下才不触发呢?
与浏览器关闭事件相关事件有onunload和onbeforeunload两个。区别在于onbeforeunload在onunload之前执行,它还可以阻止onunload的执行。因此我们着重关注onbeforeunload事件。简单科普一下onbeforeunload事件。
当窗口即将被卸载(关闭)时,会触发该事件.此时页面文档依然可见,且该事件的默认动作可以被取消. 该函数应当返回一个字符串,当返回的字符串不为null或者undefined时,弹出确认窗口让用户自行选择是否关闭当前页面。一些浏览器将该事件返回的字符串显示在弹出窗上。
既然“当窗口即将被卸载(关闭)时,会触发该事件“,那也就是说只要我关闭快就一定会触发该事件喽?然而当关闭浏览器时,未必一定会触发onbeforeunload事件。MDN上关于这个事件的触发条件是这样描述的。
为避免意外弹出窗口,除非页面已与之交互,否则浏览器可能不会显示在beforeunload事件中创建的提示,甚至根本不会显示它们。
那什么时候算是非与之交互呢?这里举个例子,一个页面连着刷新两次,第二次刷新时,就认为非与之交互,就不会触发onbeforeunload事件。,同时对于触发条件,各个浏览器之间也存在差异。具体差异汇总表如下:
说明一下,浏览器关闭事件(onbeforeunload)里已经不可以自定义弹出窗信息了。MDN中明确写道:
一些浏览器将该事件返回的字符串显示在弹出窗上。但从Firefox 4、 Chrome 51、Opera 38 和Safari 9.1开始,通用确认信息代替事件返回的字符串。
原因2:异步请求发送失败了吗?
一定失败。原因发送异步请求后,随即关闭了浏览器,这时候这次请求的”三次握手”的”第三次握手”,客户主机便不会响应服务器主机,这也就成了一个失败的请求。如图所示。
第三次握手失败
那么有什么解决办法吗?
- 关闭浏览器时发送同步请求,来保证请求发送完成。但是这样一来会产生如下问题:
a) 页面延迟几秒后再关闭,体验糟糕。
b) XMLHttpRequest规范中禁止在这个事件处理器中同步调用接口。
使用
XMLHttpRequest
发送同步请求的方式已经计划从规范中删除,不再建议开发者使用。
这个时候不熟悉XMLHttpRequest的同学也许会问:等一下,我的代码里根本就没有XMLHttpRequest这个对象,所以他的规范凭什么约束我?
相信你项目里调用接口时已经用到了ajax库,或者axios库。其实现有的ajax库都是对XMLHTTPRequest对象的一种封装,而axios是通过promise实现对ajax技术的一种封装,这样一来一切都说得通了。原来我们都在直接或者间接的使用着XMLHttpRequest对象。
2.Fetch 的keepalive属性
Fetch API提供了一套健壮的与服务器端交互的方式,提供了跨越不同平台 API 的一致接口。它提供了一个keepalive属性,保证不管发送请求的页面关闭与否,请求都会持续直到结束。不过上传数据的限制是64 KB。写法如下:
window.addEventListener(‘onbeforeunload’, { fetch('/siteAnalytics', { method: 'POST', body: getStatistics(), keepalive: true }); }
那通过Fetch API调用的接口如何添加头信息呢?以添加token为例,以下代码亲测有效。
window.addEventListener(‘onbeforeunload’, { fetch('/siteAnalytics', { method: 'POST', body: 'id=' + id + '&name=' + name + '&age=' + age, headers: { 'Content-Type': 'application/x-www-form-urlencoded', token:’ myToken’ }, keepalive: true }); }
3.SendBeacon()
SendBeacon() 方法可用于通过HTTP将少量数据异步传输到Web服务器。该方法底层的使用的是 Fetch API,这样就能明白为什么它也有少量数据(64 KB)的上传数据限制,也能明白为什么它还能在页面卸载后继续请求。它的主要优点是简单,只要用一行代码就能搞定。
window.addEventListener('unload', { navigator.sendBeacon('/siteAnalytics', getStatistics()); }
总结一下,对于浏览器关闭事件,如果我们与页面未发生交互,那么当窗口即将被卸载(关闭)时便不会触发onbeforeunload事件;同时一些主流浏览器,从某个版本开始,也不允许我们自定义弹窗信息来给予用户友好提示了;如果想在该事件中发送请求,相对于使用XMLHttpRequest对象来说,fetch API或者sendBeacon()或是更好的选择。