Javascript生成器调用异步生成器时闭包未维护?索引异常问题
异步生成器中的闭包陷阱与解决方案
问题代码与现象
数据源与处理生成器
async function* getData() { let currentPage = 0 while(true) { let records = await fetch('api/data?page='+currentPage) if (records.length == 0) return for (record of records) { yield record } currentPage++ } } function* myGenerator() { let index = 0 let gen = getData() while(true) { index++ console.log('Setup', index) yield gen.next().then(rs => { console.log('Processing', index) return rs.value.toLowerCase() }) } }
调用代码
let promises = [] let gen = myGenerator() for (let i = 0; i < 10; i++) { promises.push(gen.next().value) }
执行后会先依次输出Setup 1到Setup 10,但后续会连续输出十次Processing 10,而非预期的Processing 1到Processing 10。
原因分析
这是闭包的典型陷阱:then回调函数并没有捕获当前循环的index值,而是引用了生成器函数作用域中的同一个index变量。当所有Setup同步执行完成后,index已经被递增到10,此时所有异步回调才开始执行,自然都会读取到index的最终值10。
解决方案
方案1:用立即执行函数捕获当前index
通过立即执行函数把当前的index值封装到独立闭包中,避免后续修改影响回调:
function* myGenerator() { let index = 0 let gen = getData() while(true) { index++ console.log('Setup', index) yield gen.next().then((function(currentIndex) { return rs => { console.log('Processing', currentIndex) return rs.value.toLowerCase() } })(index)) } }
方案2:利用块级作用域保存当前index
ES6+的let具有块级作用域特性,用单独变量保存当前循环的index值:
function* myGenerator() { let index = 0 let gen = getData() while(true) { index++ let currentIndex = index // 用块级变量锁定当前值 console.log('Setup', currentIndex) yield gen.next().then(rs => { console.log('Processing', currentIndex) return rs.value.toLowerCase() }) } }
方案3:改用异步生成器(更优雅)
既然数据源本身是异步生成器,直接把myGenerator改成异步生成器,保证每步异步操作完成后再处理下一个,从根源避免闭包问题:
async function* myGenerator() { let index = 0 let gen = getData() for await (const record of gen) { index++ console.log('Setup', index) console.log('Processing', index) yield record.toLowerCase() } } // 调用方式同步化 async function run() { let promises = [] let gen = myGenerator() for (let i = 0; i < 10; i++) { promises.push((await gen.next()).value) } } run()
内容的提问来源于stack exchange,提问作者MidnightLightning
相关产品推荐
相关产品推荐

