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

模拟ReadableStream异步迭代时遭遇TypeError: (intermediate value).next is not a function问题求助

模拟ReadableStream异步迭代时遭遇TypeError: (intermediate value).next is not a function问题求助

嘿,我帮你排查到问题所在了!你这段代码报错的核心原因是异步迭代器的[Symbol.asyncIterator]方法里的this指向错误,导致for await...of循环无法获取到正确的迭代器对象,自然就找不到next方法了。

错误原因详解

在你的streamAsyncIterator函数中,返回的迭代器对象里,[Symbol.asyncIterator]用了箭头函数:

[Symbol.asyncIterator]:()=>{
  return this;
}

箭头函数的this会绑定外层函数(也就是streamAsyncIterator)的作用域,而不是当前返回的迭代器对象本身。在浏览器环境下,这个this通常指向全局对象window,完全不是我们需要的迭代器,所以for await...of尝试调用它的next方法时,就会抛出“next is not a function”的错误。

修复方案

我们只需要把[Symbol.asyncIterator]改成普通函数,或者确保它返回当前的迭代器对象即可。这里有两种简单的修复方式:

方式一:改用普通函数

把箭头函数换成普通函数,此时this会指向当前的迭代器对象:

function streamAsyncIterator(stream, preventCancel) {
  const reader= stream.getReader();
  return {
    next:()=>{
      return reader.read();
    },
    return:(value)=>{
      if(preventCancel){
        reader.releaseLock();
      }else{
        reader.cancel();
      }
      return {};
    },
    throw:(exception)=>{
      if(preventCancel){
        reader.releaseLock();
      }else{
        return reader.cancel(exception);
      }
      return exception;
    },
    // 改成普通函数,this指向当前迭代器对象
    [Symbol.asyncIterator]() {
      return this;
    }
  };
};

方式二:提前保存迭代器引用

如果坚持要用箭头函数,可以先把迭代器对象存到变量里,然后在[Symbol.asyncIterator]中返回这个变量:

function streamAsyncIterator(stream, preventCancel) {
  const reader= stream.getReader();
  // 先定义迭代器变量
  const iterator = {
    next:()=>{
      return reader.read();
    },
    return:(value)=>{
      if(preventCancel){
        reader.releaseLock();
      }else{
        reader.cancel();
      }
      return {};
    },
    throw:(exception)=>{
      if(preventCancel){
        reader.releaseLock();
      }else{
        return reader.cancel(exception);
      }
      return exception;
    },
    [Symbol.asyncIterator]:()=>{
      // 返回提前保存的迭代器引用
      return iterator;
    }
  };
  return iterator;
};

修复后的完整代码示例

class DBSource{
  constructor(){
    this.data=[1,2,3,4,5,6];
  }
  pull=(controller)=>{
    // simulates a slow DB connection
    return new Promise((res)=>{
      setTimeout(()=>{
        let c = this.data.shift();
        if(c){
          controller.enqueue(c);
        }else{
          controller.close();
        }
        res();
      },1000);
    })
  }
}

let stream = new ReadableStream(new DBSource());

function streamAsyncIterator(stream, preventCancel) {
  const reader= stream.getReader();
  return {
    next:()=>{
      return reader.read();
    },
    return:(value)=>{
      if(preventCancel){
        reader.releaseLock();
      }else{
        reader.cancel();
      }
      return {};
    },
    throw:(exception)=>{
      if(preventCancel){
        reader.releaseLock();
      }else{
        return reader.cancel(exception);
      }
      return exception;
    },
    [Symbol.asyncIterator]() {
      return this;
    }
  };
};

(async()=>{
  try{
    console.log("Started: ... ");
    for await(const a of streamAsyncIterator(stream, false)){
      console.log(a);
    }
    console.log("Loop over");
  }catch(e){
    console.log(e);
  }
})();

运行这段修复后的代码,你就能看到每隔1秒依次输出{value: 1, done: false}到{value: 6, done: false},最后输出Loop over,完全符合你的预期。

另外,如果你想模拟stream.values({ preventCancel: true })的行为,只需要在调用streamAsyncIterator时传入第二个参数为true即可,比如在循环里break时,流不会被取消,而是释放读取锁。

备注:内容来源于stack exchange,提问作者Anurag Vohra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.23 09:24:29