# 生成器和迭代器

# Generator

常规函数只会返回一个单一值(或者不返回任何值)。

而 Generator 可以按需一个接一个地返回(“yield”)多个值。它们可与 iterable 完美配合使用,从而可以轻松地创建数据流。

# Generator 函数

要创建一个 generator,我们需要一个特殊的语法结构:function*,即所谓的 “generator function”。

Generator 函数与常规函数的行为不同。在此类函数被调用时,它不会运行其代码。而是返回一个被称为 “generator object” 的特殊对象,来管理执行流程。

function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}

// "generator function" 创建了一个 "generator object"
let generator = generateSequence();
alert(generator); // [object Generator]

一个 generator 的主要方法就是 next(),当被调用时,它会恢复函数的运行,执行直到最近的 yield <value> 语句。

function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}

let generator = generateSequence();

let one = generator.next();

console.log(JSON.stringify(one)); // {value: 1, done: false}

# Generator 是可迭代的

当你看到 next() 方法,或许你已经猜到了 generator 是 可迭代(iterable)的。(译注:next() 是 iterator 的必要方法)

我们可以使用 for..of 循环遍历它所有的值:

function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}

let generator = generateSequence();

for (let value of generator) {
  alert(value); // 1,然后是 2
}

for..of 写法是不是看起来比 .next().value 优雅多了?

……但是请注意:上面这个例子会先显示 1,然后是 2,然后就没了。它不会显示 3!

这是因为当 done: true 时,for..of 循环会忽略最后一个 value。因此,如果我们想要通过 for..of 循环显示所有的结果,我们必须使用 yield 返回它们

# Iterator

异步迭代允许我们对按需通过异步请求而得到的数据进行迭代。例如,我们通过网络分段(chunk-by-chunk)下载数据时。异步生成器(generator)使这一步骤更加方便。

# 可迭代对象

假设我们有一个对象,例如下面的 range:

我们想对它使用 for..of 循环,例如 for(value of range),来获取从 1 到 5 的值。

换句话说,我们想向对象 range 添加 迭代能力。

这可以通过使用一个名为 Symbol.iterator 的特殊方法来实现:

  • 当循环开始时,该方法被 for..of 结构调用,并且它应该返回一个带有 next 方法的对象。
  • 对于每次迭代,都会为下一个值调用 next() 方法。
  • next() 方法应该以 {done: true/false, value:<loop value>} 的格式返回一个值,其中 done:true 表示循环结束。

let range = {
  from: 1,
  to: 5,

  [Symbol.iterator]() {
    // 在 for..of 循环开始时被调用一次
    return {
      current: this.from,
      last: this.to,

      next() {
        // 每次迭代时都会被调用,来获取下一个值
        if (this.current <= this.last) {
          return { done: false, value: this.current++ };
        } else {
          return { done: true };
        }
      }
    };
  }
};

for (let value of range) {
  alert(value); // 1,然后 2,然后 3,然后 4,然后 5
}

# 异步可迭代对象

当值是以异步的形式出现时,例如在 setTimeout 或者另一种延迟之后,就需要异步迭代。

最常见的场景是,对象需要发送一个网络请求以传递下一个值,稍后我们将看到一个它的真实示例。

要使对象异步迭代:

  • 使用 Symbol.asyncIterator 取代 Symbol.iterator
  • next() 方法应该返回一个 promise(带有下一个值,并且状态为 fulfilled)。
    • 关键字 async 可以实现这一点,我们可以简单地使用 async next()
  • 我们应该使用 for await (let item of iterable) 循环来迭代这样的对象。
    • 注意关键字 await

let range = {
  from: 1,
  to: 5,

  [Symbol.asyncIterator]() {
    // (1)
    return {
      current: this.from,
      last: this.to,

      async next() {
        // (2)

        // 注意:我们可以在 async next 内部使用 "await"
        await new Promise((resolve) => setTimeout(resolve, 1000)); // (3)

        if (this.current <= this.last) {
          return { done: false, value: this.current++ };
        } else {
          return { done: true };
        }
      }
    };
  }
};

(async () => {
  for await (let value of range) {
    // (4)
    alert(value); // 1,2,3,4,5
  }
})();

# 结合 generator

现在,让我们回顾一下 generator,它使我们能够写出更短的迭代代码。在大多数时候,当我们想要创建一个可迭代对象时,我们会使用 generator。





 
 
 








let range = {
  from: 1,
  to: 5,

  *[Symbol.iterator]() { // [Symbol.iterator]: function*() 的一种简写
    for(let value = this.from; value <= this.to; value++) {
      yield value;
    }
  }
};

for(let value of range) {
  alert(value); // 1,然后 2,然后 3,然后 4,然后 5
}




 
 















async function* generateSequence(start, end) {

  for (let i = start; i <= end; i++) {

    // 哇,可以使用 await 了!
    await new Promise(resolve => setTimeout(resolve, 1000));

    yield i;
  }

}

(async () => {

  let generator = generateSequence(1, 5);
  for await (let value of generator) {
    alert(value); // 1,然后 2,然后 3,然后 4,然后 5(在每个 alert 之间有延迟)
  }

})();