You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何像使用API一样使用WebSocket?解决请求响应代码管理难题

解决WebSocket请求响应分离的async/await风格方案

方案一:原生Promise+请求ID映射

这是最常用的原生实现方式,核心思路是给每个请求分配唯一标识,通过标识关联请求和响应,把异步响应转为Promise,从而支持async/await。

实现步骤:

  • 维护一个用于存储请求Promise的映射表(比如Map),键为请求ID,值为包含resolve和reject的对象
  • 封装一个发送请求的函数,生成唯一ID,把请求ID和数据一起发送到服务器,同时返回一个Promise
  • 在onmessage回调中,解析响应中的请求ID,从映射表找到对应的Promise并完成状态变更
  • 额外处理超时、连接错误等边界情况,避免Promise一直处于pending状态

代码示例:

const ws = new WebSocket("ws://whatever");
const requestMap = new Map();
let requestIdCounter = 0;

// 封装请求函数
function sendWebSocketRequest(data) {
  return new Promise((resolve, reject) => {
    // 生成唯一请求ID
    const requestId = ++requestIdCounter;
    // 存储Promise的resolve/reject
    requestMap.set(requestId, { resolve, reject });

    // 发送带ID的请求数据(需要服务器配合解析ID)
    ws.send(JSON.stringify({
      id: requestId,
      data: data
    }));

    // 设置超时处理,避免一直等待
    setTimeout(() => {
      if (requestMap.has(requestId)) {
        reject(new Error("Request timeout"));
        requestMap.delete(requestId);
      }
    }, 5000);
  });
}

// 监听WebSocket消息
ws.onmessage = function (event) {
  try {
    const response = JSON.parse(event.data);
    // 根据响应中的ID找到对应的Promise
    const handler = requestMap.get(response.id);
    if (handler) {
      handler.resolve(response.data);
      requestMap.delete(response.id);
    }
  } catch (err) {
    console.error("解析响应失败:", err);
  }
};

// 处理WebSocket错误
ws.onerror = function (error) {
  // 拒绝所有pending的请求
  for (const { reject } of requestMap.values()) {
    reject(new Error("WebSocket connection error"));
  }
  requestMap.clear();
};

// 处理WebSocket关闭
ws.onclose = function () {
  for (const { reject } of requestMap.values()) {
    reject(new Error("WebSocket connection closed"));
  }
  requestMap.clear();
};

// 使用示例(支持async/await)
async function handleClickBtn() {
  try {
    const response = await sendWebSocketRequest("request something");
    console.log("响应结果:", response);
    // 在这里直接处理响应,逻辑更集中
  } catch (err) {
    console.error("请求失败:", err);
  }
}

方案二:基于JSON-RPC规范封装

如果服务器端支持,可以遵循JSON-RPC规范来封装请求和响应,这是一种成熟的远程过程调用协议,天然支持请求ID匹配,能让代码更具规范性。

核心思路:

  • 请求格式:{"jsonrpc": "2.0", "method": "方法名", "params": "参数", "id": 请求ID}
  • 响应格式:{"jsonrpc": "2.0", "result": "响应数据", "id": 请求ID}(成功)或{"jsonrpc": "2.0", "error": 错误对象, "id": 请求ID}(失败)
  • 封装方式和方案一类似,但数据结构遵循规范,服务器端也需要按照规范返回响应

简化代码示例:

// 封装JSON-RPC请求
async function callRpcMethod(method, params) {
  const requestId = ++requestIdCounter;
  return new Promise((resolve, reject) => {
    requestMap.set(requestId, { resolve, reject });
    ws.send(JSON.stringify({
      jsonrpc: "2.0",
      method,
      params,
      id: requestId
    }));
    // 超时处理同上...
  });
}

// onmessage中解析JSON-RPC响应
ws.onmessage = function (event) {
  const response = JSON.parse(event.data);
  const handler = requestMap.get(response.id);
  if (handler) {
    if (response.error) {
      handler.reject(new Error(response.error.message));
    } else {
      handler.resolve(response.result);
    }
    requestMap.delete(response.id);
  }
};

// 使用示例
async function handleClickBtn() {
  try {
    const result = await callRpcMethod("getSomething", {});
    console.log("RPC调用结果:", result);
  } catch (err) {
    console.error("RPC调用失败:", err);
  }
}

注意事项

  • 服务器端必须配合解析请求中的ID,并在响应中返回相同的ID,否则无法匹配请求和响应
  • 要处理WebSocket连接异常、超时等情况,避免Promise一直pending
  • 如果有大量并发请求,要注意清理映射表中的过期条目,避免内存泄漏

内容的提问来源于stack exchange,提问作者Guest-01

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 16:05:22