需求背景
用户退出当前页面时,修改的数据未进行保存,需要发送接口请求实现自动保存的功能。简单分析需求可知,退出页面包含 路由切换 和 **关闭浏览器标签 **页两种情况:
- 路由切换:项目使用 Vue2 开发,离开页面时可以在 beforeDestroy 钩子函数中调用接口实现保存数据,但是这个方法只能在路由切换当前组件销毁前触发,无法监听到浏览器页面关闭的情况。
- 关闭浏览器标签页:考虑在 window.onunload 钩子函数中发送请求。
解决方案
路由切换
路由切换时,当前组件将会被销毁,由于 beforeDestroy 钩子函数是在组件销毁前被调用,可以直接拿到当前组件的 data,因此可以在 beforeDestroy 钩子函数中直接使用 axios 去调用接口,保存数据。
路由切换时调用接口保存数据的方式简单,不做过多赘述。
关闭浏览器标签页
请求时机:
回顾一下浏览器加载、刷新、卸载(关闭)时触发的事件:
- 加载时:触发 onload 事件
- 刷新时:先触发 onbeforeunload 事件,然后是 onunload 事件,最后是 onload 事件。
- 卸载时:先触发 onbeforeunload 事件,然后是 onunload 事件
刷新和卸载页面时都会先后触发 onbeforeunload 和 onunload 事件,但刷新页面的时候时不需要调用接口的,那么就需要区分刷新和关闭行为。
根据时间差来实现判断是刷新还是关闭标签:
在点击刷新或者关闭时开始计时记录下这一刻的时间戳,因为刷新和关闭 在执行onunload方法时的时间不一样,一般情况下经过测试:
- 关闭时时间差不大于3毫秒
- 刷新时即使只有一个简单的helloworld页面都不少于10毫秒
- 而一般网站网页内容更多,时间差达到了100多毫秒
mounted () {
let beginTime = 0;
let differTime = 0;
window.onbeforeunload = function () {
beginTime = new Date().getTime();
}
window.onunload = function () {
differTime = new Date().getTime() - beginTime;
// 关闭页面操作
if (differTime < 5) {
// 请求接口数据
}
}
}
接口调用方案
由于 axios 库直接调用接口,后台无法收到请求,原因是当页面关闭销毁时,由于 axios 是异步的,http 请求就直接连接不上或者断开,导致无法向后台发送数据。
解决思路是:ajax 请求换成同步的,或者保持 http 连接。
ajax 设置为同步请求
原生方式:
var xhr = new XMLHttpRequest();
//打开请求, open方法第三个参数设置为false为同步请求
xhr.open('GET','js/data.json',false); //同步请求
// 发送请求
xhr.send(null);
目前高版本浏览器都不建议使用 XMLHttpRequest 对象方式发送同步请求
jQuery 方式:使用jq插件发送ajax请求时将 async 参数设置为 false 即可。
keepAlive
fetch 中设置 keepAlive 为 true 时,即使页面处于关闭状态也会保持连接,利用这个特性,可以发送可靠的请求。另外,使用 fetch 可以非常方便设置请求参数。
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
some: "data",
}),
// 保持连接
keepalive: true,
});
navigator.sendBeacon
navigator.sendBeacon() 方法可用于通过HTTP将少量数据异步传输到Web服务器。会使用户代理在有机会时异步地向服务器发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能,这意味着:
- 数据发送是可靠的。
- 数据异步传输。
- 不影响下一导航的载入。
navigator.sendBeacon(
"/log",
JSON.stringify({
some: "data",
})
);
sendBeacon API 不支持添加请求头,可以利用 Blob 做一些改动支持请求头。
const blob = new Blob([JSON.stringify({ some: "data" })], {
type: "application/json; charset=UTF-8",
});
navigator.sendBeacon("/log", blob);
sendBeacon 本意是发送信标,所以通常是在设置页面埋点时使用,并且不需要发送太多的数据。
总结
本文总结了用户退出当前页面前发送请求问题的一些解决方法,由于我的使用场景需要接口带上额外的参数和token,因此选择了使用 fetch api 设置 keepAlive 的方式实现退出该需求的。