首页 > 解决方案 > 异步生成器类卡在无限循环javascript上

问题描述

我正在尝试使以下异步生成器工作:

class MyIterator {
  constructor(m) {
    this.collection = m;
  }

   async *[Symbol.iterator]() {
      for (let item of this.collection) {
        const resultItem = await Promise.resolve(item)
        console.log("item: ", resultItem)
        yield resultItem
      }
  }
}
(async () => {
  const iterator = new MyIterator([1,2,3])
  let times = 0
  for await (let thing of iterator) {
    console.log("thing: ", thing)

    // this is here to avoid an infinite loop
    times++
    if (times > 1000) break
  }
})()

但它最终陷入无限循环,并且thing始终未定义。

item: 1
thing: undefined
item: 2
thing: undefined
item: 3
thing: undefined (x999)

我尝试过类似的代码,但这次没有这种Promise/async行为,它似乎工作得很好。

class MyIterator {
  constructor(m) {
    this.collection = m;
  }

   *[Symbol.iterator]() {
      for (let item of this.collection) {
        console.log("item: ", item)
        yield item
      }
  }
}

const iterator = new MyIterator([1,2,3])
for (let thing of iterator) {
  console.log("thing: ", thing)
}
item: 1
thing: 1
item: 2
thing: 2
item: 3
thing: 3

标签: javascriptecmascript-6async-awaitpromisegenerator

解决方案


for await..of构造将尝试迭代异步迭代器。

使用@@asyncIterator众所周知的符号定义异步迭代器:

class MyIterator {
  constructor(m) {
    this.collection = m;
  }

   async *[Symbol.asyncIterator]() { //<-- this is async
      for (let item of this.collection) {
        const resultItem = await Promise.resolve(item)
        //console.log("item: ", resultItem)
        yield resultItem
      }
  }
}
(async () => {
  const iterator = new MyIterator([1,2,3])
  let times = 0
  for await (let thing of iterator) {
    //no infinite loop
    console.log("thing: ", thing) 
  }
})()

for await..of还可以使用产生承诺的普通迭代:

const promiseArray = [Promise.resolve("a"), Promise.resolve("b"), Promise.resolve("c")];

(async function() {
  for await(const item of promiseArray) {
    console.log(item);
  }
})()

尝试创建作为异步方法/函数的常规迭代器不起作用。

如果你想保留你@@iterator定义的方法,最好的选择是让它产生 Promise:

class MyIterator {
  constructor(m) {
    this.collection = m;
  }

   *[Symbol.iterator]() { // not async
      for (let item of this.collection) {
        yield Promise.resolve(item); //produce a promise
      }
  }
}
(async () => {
  const iterator = new MyIterator([1,2,3])
  let times = 0
  
  for await (let thing of iterator) {
    console.log("thing: ", thing)
  }
})()

虽然,如果任何承诺拒绝,这可能是一个不好的做法:

const wait = (ms, val) =>
  new Promise(res => setTimeout(res, ms, val));
const fail = (ms, val) =>
  new Promise((_, rej) => setTimeout(rej, ms, val));
  
const arr = [ 
  wait(100, 1), 
  wait(150, 2), 
  fail(0, "boom"), 
  wait(200, 3)
];

(async function(){
  try {
    for await (const item of arr) {
      console.log(item);
    }
  } catch (e) {
    console.error(e);
  }
})()


/* result in the browser console:

Uncaught (in promise) boom
1
2
boom
*/

运行上述代码段的浏览器控制台屏幕截图。 结果与片段末尾的注释相同。

但是,请注意,这些之间存在语义差异:

  • 常规迭代器产生一个 IteratorResult - 一个具有valuedone属性的对象。

const syncIterable = {
  [Symbol.iterator]() {
    return {
      next() {
        return {value: 1, done: true}
      }
    }
  }
}

const syncIterator = syncIterable[Symbol.iterator]();
console.log("sync IteratorResult", syncIterator.next());

  • 异步生成器为 IteratorResult 生成承诺

const asyncIterable = {
  [Symbol.asyncIterator]() {
    return {
      next() {
        return Promise.resolve({value: 2, done: true});
      }
    }
  }
}

const asyncIterator = asyncIterable[Symbol.asyncIterator]();
asyncIterator.next().then(result => console.log("async IteratorResult", result));

  • 最后,产生承诺的迭代器将有一个 IteratorResult ,其中value是一个承诺:

const promiseSyncIterable = {
  [Symbol.iterator]() {
    return {
      next() {
        return {value: Promise.resolve(3), done: true}
      }
    }
  }
}

const promiseSyncIterator = promiseSyncIterable[Symbol.iterator]();
const syncPromiseIteratorResult = promiseSyncIterator.next();

console.log("sync IteratorResult with promise", syncPromiseIteratorResult);
syncPromiseIteratorResult.value
  .then(value => console.log("value of sync IteratorResult with promise", value));


关于命名的旁注:MyIterator不是迭代器迭代器next()是具有产生 IteratorResult 的方法的对象。您可以迭代的对象有一个@@iterator(or @@asyncIterable) 方法,它被称为可迭代的(或分别称为异步可迭代)。


推荐阅读