这种ws服务的封装见过吗?没见过的话还不赶紧来学学???

引言

在日常开发中,我们经常会遇到需要使用 WebSocket 实现实时通信的场景。WebSocket 协议提供了一个持久的连接,使得服务器可以主动向客户端发送消息。然而,WebSocket 本身基于事件的消息处理方式并不总是那么直观,尤其是当我们习惯了使用 async/await 这样的同步编程风格时。

最近,我尝试使用 GPT 来编写代码,这个过程让我对 Promise 的用法有了新的理解。具体来说,我希望将 WebSocket 的发布订阅模式转换为 async/await 模式。换言之,我希望能够从 onMessageXXX 形式的事件处理,转变为 const res = await xxx 这样的同步等待模式。这样的需求听起来可能有些奇怪,毕竟如果需要 await 风格的请求,为什么不直接使用 HTTP 服务呢?对此,我也只能苦笑,因为刚开始时我并没有想到这一点。请不要责怪我,毕竟每个人都有学习和成长的过程。

解决方案

建立 WebSocket 连接

以下是一个简单的 WebSocket 连接函数,它会在连接失败时每隔一秒尝试重连:

javascript 复制代码
const connectWebSocket = async () => {
  while (retryCount < maxRetries) {
    try {
      ws = new WebSocket("ws://localhost:8765");
      ws.on("open", () => console.log('WebSocket connection opened.'));
      ws.on("error", (e) => {
        console.error(`Failed to connect: ${e}, retrying...`);
        ws.close();
      });
      ws.on("message", (res) => {
        console.log('Received message:', res);
      });
    } catch (e) {
      await new Promise((resolve) => setTimeout(resolve, 1000));
      retryCount++;
    }
  }
};

异步等待连接成功

为了将这种模式转换为 async/await,我们需要解决两个问题:

  1. 如何知道 WebSocket 连接已经成功建立,并在建立成功后返回一个发送消息的函数。
  2. 在发送请求后,如何利用 onmessage 事件处理函数将消息返回。

针对第一个问题,我们可以通过将 try 代码块包装在 Promise 中来解决,如下所示:

javascript 复制代码
const connectWebSocket = async () => {
  while (retryCount < maxRetries) {
    try {
      ws = new WebSocket("ws://localhost:8765");
      await new Promise((resolve, reject) => {
        ws.on("open", () => {
          console.log('WebSocket connection opened.');
          resolve();
        });
        ws.on("error", (e) => {
          console.error(`Failed to connect: ${e}, retrying...`);
          ws.close();
          reject(e);
        });
      });
      return (message) => ws.send(message);
    } catch (e) {
      await new Promise((resolve) => setTimeout(resolve, 1000));
      retryCount++;
    }
  }
};

这样,当我们调用 const sendMessage = await connectWebSocket() 时,得到的 sendMessage 函数就可以用来发送消息了。

异步等待消息回复

对于第二个问题,我们希望能够通过 const res = await reply("你好") 的方式获取响应。最初,我尝试使用 ws.onceMessage 来处理,但这在并发请求时会遇到问题,因为除了第一个请求外,其他请求的响应监听器会在第一个事件发生后被移除。

为了解决这个问题,我们可以为每个 WebSocket 请求创建一个唯一的识别码,并维护一个请求-响应映射。这样,即使并发发送多个请求,每个响应也可以根据其唯一识别码正确地分配到对应的请求。

javascript 复制代码
const connectWebSocket = async () => {
  // ...之前的代码
    await new Promise((resolve, reject) => {
        // ...之前的代码
        ws.on("message", (res) => {
            const data = JSON.parse(res);
            const handler = responseHandlers[data.key];
            if (handler) {
              handler(data); // 调用对应 key 的解析函数
              delete responseHandlers[data.key]; // 移除已调用的解析函数
            }
        });
    });
    return (text, key) => new Promise((resolve, reject) => {
        responseHandlers[key] = resolve;
        ws.send(JSON.stringify({ text, key }));
    });
};

onmessage 事件中,我们解析消息并调用对应 key 的处理函数,确保每个请求都能得到正确的响应。

完整代码如下:

javascript 复制代码
const connectWebSocket = async () => {
while (retryCount < maxRetries) {
  try {
    ws = new WebSocket("ws://localhost:8765");
    await new Promise((resolve, reject) => {
      ws.on("open", resolve);
      ws.on("error", (e) => {
        console.error(`Failed to connect: ${e}, retrying...`);
        ws.close();
        reject(e);
      });
      ws.on("message", (res) => {
        const data = JSON.parse(res);
        const handler = responseHandlers[data.key];
        if (handler) {
          handler(data); // 调用对应 key 的解析函数
          delete responseHandlers[data.key]; // 移除已调用的解析函数
        }
      });
    });

    return (text, key) =>
      new Promise((resolve, reject) => {
        responseHandlers[key] = resolve; // 存储解析函数以便稍后调用
        ws.send(JSON.stringify({ text, key }));
      });
  } catch (e) {
    await new Promise((resolve) => setTimeout(resolve, 1000));
    retryCount++;
  }
}

总结

通过这次的探索,我们成功地将 WebSocket 的发布订阅模式转换为了基于 async/await 风格的同步处理模式。这不仅提高了代码的可读性和易用性,还支持了并发请求处理。这个过程再次证明了编程中的创造性解决方案和不断的迭代改进是多么重要。无论我们的起点如何,总有改进和优化的空间。而技术,就是这样一步步前进的。

最后

这个需求是我在开发微信机器人项目的时候遇见的,已经实现了自动回复,实时天气,语音功能,绘画功能,定时功能,项目已开源,如果有感兴趣的小伙伴,欢迎来star。

GitHub地址

顺便宣传一下另一个开源脚手架项目create-neat。已有130+star,也可加v:a2171077189一起学习。

相关推荐
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang1 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui6 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui