模拟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

