Deno oak框架WebSocket缺少[Symbol.asyncIterator]方法报错如何修复
问题描述
尝试使用oak框架(非Deno原生WebSocket实现)搭建WebSocket服务,服务端构建代码如下:
import {Application, Router, Context, send } from "https://deno.land/x/oak@v10.6.0/mod.ts"; const runWS = async (ctx: Context, next: () => Promise<unknown>) => { try{ const ws = await ctx.upgrade(); ws.onopen = () => { chatConnection(ws); }; ws.onclose = () => { console.log('Disconnected from the client!');}; }catch{await next();} } let sockets = new Map<string, WebSocket>(); const chatConnection = async (ws: WebSocket) => { console.log('new websocket, ws: ',ws); const uid = globalThis.crypto.randomUUID(); sockets.set(uid,ws); console.log('socket: ',sockets); for await (const ev of ws){ console.log('ev: ', ev); } } export const wsRoutes = new Router() .get('/ws', runWS);
在代码末尾的for await...of循环遍历ws实例时,抛出类型错误:
Type 'WebSocket' must have a 'Symbol.asyncIterator' method that returns an async iterator.
错误产生原因
ctx.upgrade()返回的是遵循标准Web API规范的WebSocket实例,该原生实例未实现[Symbol.asyncIterator]异步迭代接口,不支持直接被for await...of语法遍历。- 写法混淆了Deno原生HTTP模块与oak框架返回的WebSocket实例差异:Deno原生HTTP模块升级得到的WebSocket额外封装了异步迭代能力,支持直接
for await...of遍历事件,但oak v10.6.0版本返回的WebSocket严格对齐Web标准,没有提供该扩展能力。
修复方案
方案1:使用标准事件回调写法(推荐)
直接使用WebSocket原生支持的事件监听API处理消息、错误、断开事件,无需额外封装,兼容性最好:
const chatConnection = async (ws: WebSocket) => { console.log('new websocket, ws: ',ws); const uid = globalThis.crypto.randomUUID(); sockets.set(uid,ws); console.log('socket: ',sockets); // 替换原有的for await遍历逻辑 ws.onmessage = (ev) => { console.log('ev: ', ev); // 在此处编写消息处理、广播等业务逻辑 } ws.onerror = (err) => { console.error('websocket connection error: ', err); } ws.onclose = () => { // 连接断开时清理存储的实例 sockets.delete(uid); console.log('Disconnected from the client!'); } }
方案2:手动包装异步迭代器适配for await写法
如果需要使用for await...of的线性逻辑写法,可以手动将WebSocket实例包装为异步可迭代对象:
// 包装WebSocket为异步可迭代对象的工具函数 const wsToAsyncIterable = (ws: WebSocket): AsyncIterable<MessageEvent> => { return { [Symbol.asyncIterator]() { const eventQueue: MessageEvent[] = []; let pendingResolve: ((result: IteratorResult<MessageEvent>) => void) | null = null; let isClosed = false; ws.onmessage = (event) => { if (pendingResolve) { pendingResolve({ value: event, done: false }); pendingResolve = null; } else { eventQueue.push(event); } }; ws.onclose = () => { isClosed = true; if (pendingResolve) { pendingResolve({ value: undefined as any, done: true }); pendingResolve = null; } }; return { next() { if (eventQueue.length > 0) { return Promise.resolve({ value: eventQueue.shift()!, done: false }); } if (isClosed) { return Promise.resolve({ value: undefined as any, done: true }); } return new Promise((resolve) => { pendingResolve = resolve; }); } } } } } const chatConnection = async (ws: WebSocket) => { console.log('new websocket, ws: ',ws); const uid = globalThis.crypto.randomUUID(); sockets.set(uid,ws); console.log('socket: ',sockets); // 包装后即可正常使用for await遍历 for await (const ev of wsToAsyncIterable(ws)){ console.log('ev: ', ev); // 在此处编写消息处理逻辑 } // 遍历结束代表连接已断开,清理实例 sockets.delete(uid); }
内容的提问来源于stack exchange,提问作者Tinaira
相关产品推荐
相关产品推荐

