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

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