同步遍历器的问题

《遍历器》一章说过,Iterator 接口是一种数据遍历的协议,只要调用遍历器对象的next方法,就会得到一个对象,表示当前遍历指针所在的那个位置的信息。next方法返回的对象的结构是{value, done},其中value表示当前的数据的值,done是一个布尔值,表示遍历是否结束。

  1. function idMaker() {
  2. let index = 0;
  3. return {
  4. next: function() {
  5. return { value: index++, done: false };
  6. }
  7. };
  8. }
  9. const it = idMaker();
  10. it.next().value // 0
  11. it.next().value // 1
  12. it.next().value // 2
  13. // ...

上面代码中,变量it是一个遍历器(iterator)。每次调用it.next()方法,就返回一个对象,表示当前遍历位置的信息。

这里隐含着一个规定,it.next()方法必须是同步的,只要调用就必须立刻返回值。也就是说,一旦执行it.next()方法,就必须同步地得到valuedone这两个属性。如果遍历指针正好指向同步操作,当然没有问题,但对于异步操作,就不太合适了。

  1. function idMaker() {
  2. let index = 0;
  3. return {
  4. next: function() {
  5. return new Promise(function (resolve, reject) {
  6. setTimeout(() => {
  7. resolve({ value: index++, done: false });
  8. }, 1000);
  9. });
  10. }
  11. };
  12. }

上面代码中,next()方法返回的是一个 Promise 对象,这样就不行,不符合 Iterator 协议,只要代码里面包含异步操作都不行。也就是说,Iterator 协议里面next()方法只能包含同步操作。

目前的解决方法是,将异步操作包装成 Thunk 函数或者 Promise 对象,即next()方法返回值的value属性是一个 Thunk 函数或者 Promise 对象,等待以后返回真正的值,而done属性则还是同步产生的。

  1. function idMaker() {
  2. let index = 0;
  3. return {
  4. next: function() {
  5. return {
  6. value: new Promise(resolve => setTimeout(() => resolve(index++), 1000)),
  7. done: false
  8. };
  9. }
  10. };
  11. }
  12. const it = idMaker();
  13. it.next().value.then(o => console.log(o)) // 1
  14. it.next().value.then(o => console.log(o)) // 2
  15. it.next().value.then(o => console.log(o)) // 3
  16. // ...

上面代码中,value属性的返回值是一个 Promise 对象,用来放置异步操作。但是这样写很麻烦,不太符合直觉,语义也比较绕。

ES2018 引入了“异步遍历器”(Async Iterator),为异步操作提供原生的遍历器接口,即valuedone这两个属性都是异步产生。