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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 22:53:12