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

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

感谢彦祖们的阅读

个人能力有限

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

相关推荐
辛-夷4 分钟前
VUE面试题(单页应用及其首屏加载速度慢的问题)
前端·javascript·vue.js
田哥coder5 分钟前
充电桩项目:前端实现
前端
青年有志18 分钟前
Web 服务器介绍 | 通过 Tomcat 说明其作用
服务器·前端·tomcat
dawn19122837 分钟前
SpringMVC 中的域对象共享数据
java·前端·servlet
newxtc1 小时前
【爱给网-注册安全分析报告-无验证方式导致安全隐患】
前端·chrome·windows·安全·媒体
一个很帅的帅哥2 小时前
axios(基于Promise的HTTP客户端) 与 `async` 和 `await` 结合使用
javascript·网络·网络协议·http·async·promise·await
dream_ready2 小时前
linux安装nginx+前端部署vue项目(实际测试react项目也可以)
前端·javascript·vue.js·nginx·react·html5
编写美好前程2 小时前
ruoyi-vue若依前端是如何防止接口重复请求
前端·javascript·vue.js
flytam2 小时前
ES5 在 Web 上的现状
前端·javascript
喵喵酱仔__2 小时前
阻止冒泡事件
前端·javascript·vue.js