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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 01:15:28