Web Beacon 刷新/关闭页面之前发送请求
本文中提到的链接,因为微信的限制,没有显示出来,查看文中链接,需要点击最下方的阅读原文链接
修订版:主要增加了sendBeacon的内容(在后面),值得一读
背景:
有一个任务非常耗时会消耗后台大量算力,所以在退出页面的时候,要求前端这边发送一个请求来杀死任务。
一开始以为这个需求非常简单,就是在进入其他路由前,发送一下请求,杀死一下任务就好了。
然而现实狠狠的打了我的脸,因为退出页面的场景不止切换路由~
退出页面场景:
- 还在本网站,跳到其他路由
- 刷新页面/关闭页面也需要发送请求来杀死任务
还在本网站,跳到其他路由
这个比较简单,在 Vue
中可以通过路由离开的钩子 beforeRouteLeave
来实现:
beforeRouteLeave(to, from, next) {
if (任务运行中) {
// 发送请求
}else{
next(true) // 用户离开
}
}
刷新页面/关闭页面的情况:
然而在刷新页面的时候, beforeRouteLeave
并不会执行,接着想到了下面这两个 API
.
beforeunload
和 unload
beforeunload 当浏览器窗口关闭或者刷新时触发:
介绍:
使用这个 API
可以阻止页面直接关闭,用户通过点击确定/取消按钮,来决定是否不关闭/刷新当前页面。
在 chrome 下长这个样子,你们肯定都见过:
如何使用
这个 API 的使用非常简单,只要在页面加载的时候监听一下此事件,在需要出现弹窗的时候return 一个可以转化为 true 的值,就可以了。
// 页面卸载之前
let killTask = false; // 是否杀死任务
window.onbeforeunload = e => {
if (任务运行 && 对应页面) {
killTask = true;
return '您可能有数据没有保存'; // 在部分浏览器可以修改弹窗标题
} else {
killTask = false;
}
// 没有return一个可以转化为true的值 就不会出现弹窗
};
出现此弹窗的浏览器行为:
以下行为是基于 chorme:
- 焦点:你没有点击取消/确定之前,焦点会一直在此弹窗上
- 你无法在出现弹窗的页面上执行任何操作
- 在其他页面也只能执行简单的点击操作,弹窗还是存在页面中间,无法使用键盘,
-
键盘:键盘被绑定在弹窗上,只能通过按键
Esc
、Enter
来执行取消/确定操作 - 弹窗不是页面的 dom,是浏览器的行为
- 用户取消/确定,没有回调 API,无法得知
弹窗标题:
chrome 中刷新页面的标题: 重新加载此网站?
chrome 中关闭页面的标题: 离开此网站?
现在大部分浏览器都不允许修改弹窗的标题,这个是为了安全考虑,来保证用户不受到错误信息的误导,
迷茫:
一开始我以为既然可以拦截到用户的刷新/关闭页面的操作,出现了上面那个弹窗,这个需求就已经做完了的时候。
然后发现,浏览器竟然没有提供用户点击确定/取消刷新页面的回调。
到这里我陷入了迷茫,盯着 beforeunload
这个 API 思考了起了人生的意义(其实是在发呆),盯着盯着,从 beforeunload
的 before
我也就想到了 unload
这个 API。
瞬间又燃起了斗志,何不试试这个 unload
?
unload
当页面正在被卸载的时候触发该事件
介绍
当页面正在被卸载的时候触发该事件,该事件不可取消,为不可逆操作。
使用
直接监听该事件就可以了。
window.onunload = e => {}
结合需求:
killTask
为 beforeunload
时定义的变量,每次进入回调,都会给 killTask
赋值,使用这个值就可以判断什么时候可以发送请求杀死任务。
window.onunload = e => {
if (killTask && 对应页面) {
// 发送请求
}
};
到这里大家肯定以为已经做出来了该需求,事实上,并没有!
无法发送异步请求
我使用的是 axios
来发送请求,请求发出去了,但是被取消了,服务器那边根本没有收到请求,如下。
经过一顿分析:发现是 axios
请求是异步的问题,谷歌之后发现axios不支持同步的请求
最后使用原生的XMLHttpRequest对象,让请求同步
~~大功告成!~~ 实际上,上面才是我第一次要发的内容,而下面更好的解决方法!
缺陷与更好的建议:
当我把这篇文章发布在公众号上,被奇舞周刊转载了,上面一些大佬给我提了一些建议。
研究了一下,结果...好吧!我承认我是菜鸡。
hey~ 不过这正是我写博客的收获之一,分享经验,收获知识!
性能缺陷:
XHR同步请求会阻碍页面卸载,如果是刷新/跳转页面的话,页面重新展示速度会变慢,导致性能问题。
毕竟向网络发送请求并获得响应可能会超级慢,有可能是用户网络环境比较差,又或者是服务器挂了,请求一直没返回回来...
基于性能问题,大佬们推荐使用Beacon代替XHR,然后经过一番搜索...
Beacon API
- Beacon API用于将少量数据通过post请求发送到服务器。
Beacon
是非阻塞请求,不需要响应
完美解决性能缺陷问题:
- 浏览器将
Beacon
请求排队让它在空闲的时候执行并立即返回控制 - 它在
unload
状态下也可以异步发送,不阻塞页面刷新/跳转等操作。
所以 Beacon
可以完美解决上面提到的因XHR同步请求阻塞而引起的性能缺陷问题。
使用: navigator.sendBeacon()
完整API:
let result = navigator.sendBeacon(url, data);
Beacon
是挂在 navigator
下面的,上面就是它的完整API。
result
是一个布尔值,代表这次发送请求的结果:
- 如果浏览器接受并且把请求排队了则返回 tru
- 如果在这个过程中出现了问题就返回 false
navigator.sendBeacon
接受两个参数:
- url: 请求的 URL。请求是 POST 请求。
- data: 要发送的数据。 数据类型可以是:ArrayBufferView, Blob, FormData,Sting。
来看一个用 FormData
来传递数据的栗子,你就懂了:
// 创建一个新的 FormData 并添加一个键值对
let data = new FormData();
data.append('hello', 'world');
let result = navigator.sendBeacon('./src', data);
if (result) {
console.log('请求成功排队 等待执行');
} else {
console.log('失败');
}
浏览器支持:
浏览器支持:Edge:14+,Firefox:31+,Chrome:39+,Opera:26+,IE:不支持。
虽然现在浏览器对 sendBeacon
的支持很好,我们对其做一下兼容性处理也是有必要的:
if (navigator.sendBeacon) {
// Beacon 代码
} else {
// 回退到 XHR同步请求或者不做处理
}
web wroker中使用Beacon
因为 Beacon
是挂在 navigator
下面,而web worker也有 navigator
,去找了一下,真的给我找到了。
这儿有一个MDN提供的栗子,可以点进去看一下。
PS:对web worker不熟悉的同学可以看我这篇文章
Beacon其他相关
- 客户端优化:可以将 Beacon 请求合并到其他请求上,一同处理, 尤其在移动环境下。
- Beacon更多的情况是用于做前端埋点,监控用户活动,它的初衷也基于此。
小结
本文总共讲了三个API, beforeunload
、 unload
和 Beacon
, Beacon
这个API估计知道的人比较少,以后遇到前端埋点和页面卸载前发送请求的需求,记得使用这三个API。
以上2019.02.19
博客、前端积累文档、公众号、GitHub
参考资料:
MDN
页面跳转时,统计数据丢失问题探讨
使用 Web Beacon API 记录活动
以上,希望本文能够对你有一些帮助。
种一颗树最好的时间是十年前,其次就是现在了!
点击下方,阅读原文,访问文中链接吧!
- MYSQL数据库设计的一些小技巧[转载]
- 在ASP.NET 5应用程序中的跨域请求功能详解什么是“同域”添加CORS包在应用程序中配置CORSCORS策略选项跨域请求中的凭据设置先行请求的过期时间CORS是怎么样工作的先行请求
- 有趣的算法(八) ——红黑树插入算法
- 使用Donut Caching和Donut Hole Caching在ASP.NET MVC应用中缓存页面何时使用Donut CachingDonut Caching 的Nuget 包Donut Ho
- 有趣的算法(九) ——蛇形数组
- 有趣的算法(十一) ——分治法:快速求最值
- ASP.NET AJAX(3)__UpdatePanel
- 正则表达式学习笔记
- PHP10个实用函数
- ASP.NET AJAX(2)__ASP.NET 2.0 AJAX Extensions
- Android利用V4包中的SwipeRefreshLayout实现上拉加载
- 在Scala项目中使用Spring Cloud
- Scala的面向对象与函数编程
- ASP.NET AJAX(1)__Microsoft AJAX LibraryASP.NET AJAX(1)__Microsoft AJAX Library
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- python中get和post有什么区别
- workerman结合laravel开发在线聊天应用的示例代码
- php+js实现裁剪任意形状图片
- keras实现theano和tensorflow训练的模型相互转换
- python中round函数如何使用
- PHP array_reduce()函数的应用解析
- php微信公众号开发之简答题
- php5.x禁用eval的操作方法
- php微信公众号开发之图片回复
- php微信公众号开发之答题连闯三关
- swoole_process实现进程池的方法示例
- golang实现php里的serialize()和unserialize()序列和反序列方法详解
- keras 实现轻量级网络ShuffleNet教程
- Python应用实现处理excel数据过程解析
- Python实现爬取并分析电商评论