需求背景

用户退出当前页面时,修改的数据未进行保存,需要发送接口请求实现自动保存的功能。简单分析需求可知,退出页面包含 路由切换 和 **关闭浏览器标签 **页两种情况:

  • 路由切换:项目使用 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 的方式实现退出该需求的。