这种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一起学习。

相关推荐
JUNAI_Strive_ving13 分钟前
番茄小说逆向爬取
javascript·python
看到请催我学习22 分钟前
如何实现两个标签页之间的通信
javascript·css·typescript·node.js·html5
twins352041 分钟前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
qiyi.sky1 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~1 小时前
分析JS Crash(进程崩溃)
java·前端·javascript
哪 吒1 小时前
华为OD机试 - 几何平均值最大子数(Python/JS/C/C++ 2024 E卷 200分)
javascript·python·华为od
安冬的码畜日常1 小时前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
l1x1n02 小时前
No.3 笔记 | Web安全基础:Web1.0 - 3.0 发展史
前端·http·html
Q_w77422 小时前
一个真实可用的登录界面!
javascript·mysql·php·html5·网站登录
昨天;明天。今天。2 小时前
案例-任务清单
前端·javascript·css