什么,你还不会WebSocket 的异步 close 方法?

前言

彦祖们,阅读本文前,希望你对 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 方法

不过处理方式也非常简单,彦祖们只需要自己封装一个类,把这些公共逻辑都封装起来就行了

评论区如果呼声比较高,下期就带彦祖们一起封装一个支持异步 closeWebSocket 类吧~

写在最后

实际业务场景中,也是小概率会遇到这种 bug, 算是一个比较难复现的 bug 吧

但是彦祖们,因此而收获的技术积累却是可遇而不可求的,共勉!

感谢彦祖们的阅读

个人能力有限

如有不对,欢迎指正 🌟 如有帮助,建议小心心大拇指三连🌟

相关推荐
new出一个对象1 小时前
uniapp接入BMapGL百度地图
javascript·百度·uni-app
你挚爱的强哥2 小时前
✅✅✅【Vue.js】sd.js基于jQuery Ajax最新原生完整版for凯哥API版本
javascript·vue.js·jquery
y先森3 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy3 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189113 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿4 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡5 小时前
commitlint校验git提交信息
前端
天天进步20155 小时前
Vue+Springboot用Websocket实现协同编辑
vue.js·spring boot·websocket
虾球xz5 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇6 小时前
HTML常用表格与标签
前端·html