最近在做编辑器相关的业务,底层是基于textbus这个富文本库,其中也踩了很多坑,后续再整理系列的文章吧。 本次主要记录一下基于百度脑图二次开发的脑图中,遇到一个场景,刷新浏览器和关闭浏览器的时候,需要发送一个接口到后端进行,并且这次接口请求必须要发送到后端,不能说该接口请求可有可无。
背景:脑图目前对协调编辑支持度不是很好,所以引入了一个锁的概念,即在当前团队笔记本下,如果某脑图有人正在编辑,其他人点击编辑的时候,就会被toast提示有人正在编辑的相关文案,既然前面我们引入了锁的概念,那么就要在后续离开编辑的页面的时候,释放掉该锁。目前我们针对以下几种场景进行了加锁,
- 同一个人不能同时进入同一篇脑图的编辑页面,即如果通过复制链接或者分享的方式进入,页面内部会判断该锁的所属状态,然后将其从编辑页面退出到阅读模式。
- 在离开页面的场景:刷新浏览器,关闭浏览器,离开当前的页面路由(借助vue的router 钩子函数去处理)去释放锁。
那么这里我们在刷新浏览器或者关闭浏览器场景下,就遇到了一点难题:
我们知道 ,刷新浏览器的时候,主要涉及到两个事件,beforeunload, unload。后续再详细讲下beforeunload, unload这两个事件的区别。
一开始是用axios正常的在beforeunload, unload进行去做释放锁的功能,但是发现不能将该接口正常的发送到后端,背后的原因了解到是因为当页面关闭销毁时,由于 axios 是异步的,http 请求就直接连接不上或者断开,导致无法向后台发送数据。
查询了资料,想起来以前做埋点的时候,也有类似的场景,即用了
navigator.sendBeacon()这个API,这里就没有单独抽取函数了,大致写法如下:
window.addEventListener('onbeforeunload', (e) => {
e.preventdefault()
const url = 'api/xxx/update' // 保存脑图的信息,因为防止异常情况用户输入的内容被丢失了,会定时的去保存他输入的内容,通过 autoSave: true, 自动保存,通过字段isLockRelease: true 来释放锁,false,则不释放锁,只进行保存
const data = {
content: xmindInfo, // 脑图信息
id: xmindId,
autoSave: true, 自动保存,
isLockRelease: false // false, 这里只保存脑图的信息,不释放锁,
}
const blob = new Blob([ JSON.stringify(data) ], {
type: 'application/json; charset=UTF-8',
}
navigator.sendBeacon(url, blob);
e.returnValue = '关闭提示'
return '关闭提示'
})
window.addEventListener('unload', () => {
// 释放锁的接口
const url = '/api/xxx/removeXmindLock'
const data = {id: xmindId}
const blob = new Blob([ JSON.stringify(data) ])
navigator.sendBeacon(url, blob);
})
按照上面的写法,大部分浏览器能实现在刷新浏览器或针关闭浏览器的时候,成功保存脑图的数据并且将锁给释放掉。
但是,前端总是会遇到那么些意想不到兼容性的东西呀
在一些使用chrome75版本的用户中,处在编辑脑图页面的时候,进行刷新浏览器的时候,出问题了,why?? 问题出在哪了呢?
只能尝试去复现用户触发场景了,安装特定浏览器版本,根据操作路径一点一点去排查,安装抓包工具,一顿操作...
难过的事情来了,出问题的地方在刷新浏览器的时机,打开后端的日志,没有看到接口请求进来,但是,前端这边,这时候看不到控制台是否有报错 ^_^
这时候只能靠录屏了,快捷键打开window自带的录屏软件,重复一次复现操作路径,这一次在录屏里面看到了报错:DOMExpection: failed to execute 'sendBeacon' on 'Navigator': sendBeacon() width a Blob whose type is not any of tthe CORS_safelisted values for the Content-Type request header is disabled temporarily,并给出了这个链<https://bugs.chromium.org/p/chromium/issues/detail?id=490015,
这时候就上gpt了,
这个链接是一个 Chromium 的 bug 追踪记录,主要记录了 Chrome 浏览器在处理使用
sendBeacon方法发送的Blob对象的 Content-Type 时出现的问题。在 Chrome 75 版本中,浏览器对于使用sendBeacon方法发送的Blob对象的 Content-Type 进行了限制,只有特定的 Content-Type 值才被允许使用,其中包括text/plain和application/x-www-form-urlencoded。如果使用其他类型的 Content-Type,浏览器会将其替换为text/plain类型。Q:bugs.chromium.org/p/chromium/…
A: 这个 bug 追踪记录中提到了一些开发者在使用
sendBeacon方法发送Blob对象时遇到的问题,以及一些解决方法。其中,建议开发者将Blob对象的type属性修改为text/plain;charset=UTF-8,然后在服务器端进行解析。同时,也提到了可以使用FormData对象来发送数据的方法,这种方法可以避免修改Blob对象的type属性,同时也可以确保数据能够被服务器端正确解析
这时候,把该接口的类型换成formData类型的,重复操作复现路径,后端接口还是没有进来日志,这时候想,前端是否把该接口发送出去了呢?这时候只能抓包了,因为 navigator.sendBeacon(url, data);,发送的请求,在浏览器network里面看不到,只能抓包,在抓包软件里面wireshark里面看到,后端这个接口不支持这个formData类型的。
问题找到了,就让后端提供了一个相同功能的,content-type为formData的接收类型的接口,以为可以完成解决该bug, 没有想到,此时并没有结束 -- 刷新浏览器,锁没有被释放掉!! 因为锁的释放在unload事件里面,这时候对比两个浏览器刷新的时候,看抓包软件里面抓到的接口是否都有,发现在chrome75版本的时候,没有走到unload事件里面,没有执行里面的接口请求!! 最后,只能在beforeunload事件里面去处理释放锁了
不同的浏览器执行unload的事件顺序可能不一致, 这可能是因为Chrome 75引入了一项新的功能:“页面卸载阻止”。此功能旨在提高用户体验,通过延迟unload事件的触发来允许网页执行更多的清理操作。因此,在Chrome 75中,unload事件可能会被推迟或完全省略。
- 如果您需要确保在浏览器关闭或刷新时执行某些操作,建议使用beforeunload事件而不是unload事件。beforeunload事件可以防止用户意外关闭页面,并提供一个机会来询问用户是否要保存未保存的更改这种行为可能会随着浏览器版本的更新而发生变化,因此最好不要依赖unload事件来执行关键操作。 unload [MDN: developer.mozilla.org/en-US/docs/…] beforeunload [MDN: developer.mozilla.org/zh-CN/docs/…]
let isSuccess = navigator.sendBeacon(url, data);
参数:
url是你要发送到后端的接口URL,
data是你该接口要额外传的参数,注意data参数只支持发、送ArrayBufferView或Blob、DOMString或者FormData类型返回值:如果用户代理成功地将数据排队等待传输则返回true, 但是这时候并不代表你接口成功发送到后端了。否则,返回false。 注意:该API是异步请求,只支持post请求