前言
彦祖们,阅读本文前,希望你对 WebSocket 有基础的了解
业务场景复现
笔者在最近的项目中接手了这样一个坑
笔者用最简单的代码,模拟了一下这个场景
大致意思是先创建了一个 ws
, 500ms 后关闭这个 ws
同时又新建了一个 ws
js
let status = 'open'
let ws = null
function closeWs(){
ws.close()
}
function createWs(){
ws = new WebSocket('ws://localhost:8080')
ws.onopen = ()=>{
status = 'open'
}
ws.onclose = ()=>{
status = 'close'
}
}
function initWs(){
closeWs()
createWs()
}
createWs()
setTimeout(()=>{
initWs()
},500)
乍眼一看,这代码的确是没什么问题,其实线上也已经稳定运行了一年多了
但是最近有一家客户却反映了这个问题
ws
明明是 open
的,但是界面上显示的却是 close
的
定位问题
其实这种问题也比较好定位,仔细观察一下 onclose
onopen
都是回调函数
那么如果 closeWs
触发的onclose
的回调比 createWs
触发的onopen
回调更晚执行的话
就会导致这个 bug
解决问题
这个问题解决的方式有好多种,但是今天我们的主题是封装一个异步 close
方法
我们期待的使用方式
js
ws.asyncClose().then(res=>{
// 成功关闭后的逻辑
})
开始封装
挂载 asyncClose 方法
我们给 WebSocket
原型上挂载一个异步关闭方法 asyncClose
但是笔者不是很建议操作原型(比如说之后 WebSocket
新出了一个同名方法,就会产生 bug
)
本文只做技术讲解,彦祖们可以封装一个自己的类 extends
一下 WebSocket
好了,废话不多说,看下此时的函数签名
js
// code reason 即源生 ws 的 code reason
WebSocket.prototype.asyncClose = function(code,reason){
}
实现 asyncClose 方法
既然我们是异步关闭,那么自然会返回一个 promise
笔者在这一步遇到两个难点
1.如何在方法内部获取 ws
实例?
2.promise
完成的理想时机?
不断实践,给出方案
问题 1
既然我们调用的方式是 ws.asyncClose
,那么对应的方法内部关键字 this
不就是 ws
吗?
问题 2
完成的时机非常明朗,需要在 onclose
函数触发,那么在asyncClose
内部,我们只需要做赋值
不做调用
核心代码
有了方案,代码也自然非常清晰了
js
WebSocket.prototype.asyncClose = function(code,reason){
// 此处一定要保证 this 的准确性
const ws = this
ws.close(code,reason)
return new Promise(resolve=>{
ws.resolve = resolve // 只做赋值, 需要在 onclose 回调中执行 resolve
})
}
完整代码
让我们用封装的方法来测试一下,调用方式也非常简单
js
WebSocket.prototype.asyncClose = function(code,reason){
// 此处一定要保证 this 的准确性
const ws = this
ws.close(code,reason)
return new Promise(resolve=>{
ws.resolve = resolve // 只做赋值, 需要在 onclose 回调中执行 resolve
})
}
let status = 'open'
function createWs(){
ws = new WebSocket('ws://localhost:8080')
ws.onopen = ()=>{
status = 'open'
}
ws.onclose = (e) => {
status = 'close'
ws.resolve?.(e)
ws.resolve = null
}
}
function initWs(){
ws.asyncClose().then(createWs) // 保证关闭完成后 再创建
}
createWs()
// 模拟 500ms 后先关闭再打开
setTimeout(()=>{
initWs()
},500)
缺陷分析
这种方式有一个缺陷,那就是需要用户在 onclose
回调中手动调用 resolve
方法
不过处理方式也非常简单,彦祖们只需要自己封装一个类,把这些公共逻辑都封装起来就行了
评论区如果呼声比较高,下期就带彦祖们一起封装一个支持异步 close
的 WebSocket
类吧~
写在最后
实际业务场景中,也是小概率会遇到这种 bug, 算是一个比较难复现的 bug 吧
但是彦祖们,因此而收获的技术积累却是可遇而不可求的,共勉!
感谢彦祖们的阅读
个人能力有限
如有不对,欢迎指正 🌟 如有帮助,建议小心心大拇指三连🌟