如何像使用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
相关产品推荐
相关产品推荐

