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

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

感谢彦祖们的阅读

个人能力有限

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

相关推荐
天宇&嘘月2 小时前
web第三次作业
前端·javascript·css
小王不会写code2 小时前
axios
前端·javascript·axios
发呆的薇薇°4 小时前
vue3 配置@根路径
前端·vue.js
luckyext4 小时前
HBuilderX中,VUE生成随机数字,vue调用随机数函数
前端·javascript·vue.js·微信小程序·小程序
小小码农(找工作版)4 小时前
JavaScript 前端面试 4(作用域链、this)
前端·javascript·面试
前端没钱4 小时前
前端需要学习 Docker 吗?
前端·学习·docker
前端郭德纲4 小时前
前端自动化部署的极简方案
运维·前端·自动化
海绵宝宝_5 小时前
【HarmonyOS NEXT】获取正式应用签名证书的签名信息
android·前端·华为·harmonyos·鸿蒙·鸿蒙应用开发
码农土豆5 小时前
chrome V3插件开发,调用 chrome.action.setIcon,提示路径找不到
前端·chrome
鱼樱前端5 小时前
深入JavaScript引擎与模块加载机制:从V8原理到模块化实战
前端·javascript