什么,你还不会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 吧

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

感谢彦祖们的阅读

个人能力有限

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

相关推荐
好名字082131 分钟前
前端取Content-Disposition中的filename字段与解码(vue)
前端·javascript·vue.js·前端框架
摇光9336 分钟前
js高阶-async与事件循环
开发语言·javascript·事件循环·宏任务·微任务
隐形喷火龙42 分钟前
element ui--下拉根据拼音首字母过滤
前端·vue.js·ui
m0_748241121 小时前
Selenium之Web元素定位
前端·selenium·测试工具
等一场春雨1 小时前
springboot 3 websocket react 系统提示,选手实时数据更新监控
spring boot·websocket·react.js
风无雨1 小时前
react杂乱笔记(一)
前端·笔记·react.js
胡西风_foxww1 小时前
【ES6复习笔记】Class类(15)
javascript·笔记·es6·继承··class·静态成员
鑫~阳1 小时前
快速建站(网站如何在自己的电脑里跑起来) 详细步骤 一
前端·内容管理系统cms
却道天凉_好个秋1 小时前
音视频学习(二十八):websocket-flv
websocket·音视频·flv
egekm_sefg1 小时前
webrtc学习----前端推流拉流,局域网socket版,一对多
前端·学习·webrtc