# 十一、生成器

生成器是可以暂停和恢复的功能。它们使用`function*`和`yield`关键词简化迭代器创作。声明为`function*`的函数返回一个`Generator`实例。生成器是迭代器的子类型，包括附加的`next`和`throw` 函数。这些使值能够流回生成器，因此`yield`是返回或抛出一个值的表达式形式。

如前一段所述，生成器是可以暂停和恢复的功能。让我们看一下下面这个简单的例子，以便更好地理解生成器:

代码清单 145

```js
  function* sampleFunc() {

  console.log('First');

  yield;

  console.log('Second'); 
  }

  let gen = sampleFunc();
  console.log(gen.next());
  console.log(gen.next());

```

在这里，我们创建了一个名为`sampleFunc`的生成器。请注意，我们使用`yield`关键字在函数中间暂停。还要注意，当我们调用函数时，它不会被执行。在我们迭代函数之前，它不会被执行，所以这里我们使用`next`函数迭代它。`next`功能将一直执行，直到达到`yield`状态。这里它将暂停，直到调用另一个`next`功能。这又会恢复并继续执行，直到函数结束。

让我们看一下输出，以获得进一步的说明:

代码清单 146

```js
  First
  { value: undefined, done: false }
  Second
  { value: undefined, done: true }

```

现在让我们进入下一部分，看看生成器迭代。

让我们看一个例子，创建一个生成器，然后使用它进行迭代。考虑下面的代码示例:

代码清单 147

```js
  let fibonacci = {

  [Symbol.iterator]: function*()
  {

  let pre =
  0, cur =
  1;

  for (;;) {

  let temp =
  pre;

  pre = cur;

  cur += temp;

  yield cur;

  }

  }
  }

  for (let n of
  fibonacci) {

  // truncate the sequence at 100

  if (n >
  100)

  break;

  console.log(n);
  }

```

从前面的代码中可以看出，生成器确实是迭代器的一个子类型。语法非常相似，只是有一些变化。值得注意的是，你会看到`function*`和`yield`关键词。

以下是前面代码的输出:

代码清单 148

```js
  1
  2
  3
  5
  8
  13
  21
  34
  55
  89

```

如您所见，我们得到了与编写自己的迭代器时相同的输出。

让我们用更多的 ES6 风格的编码再写一遍:

代码清单 149

```js
  let fibonacci = {

  *[Symbol.iterator]() {

  let pre =
  0, cur =
  1;

  for (;;) {

  [ pre, cur ] = [ cur, pre + cur ];

  yield cur;

  }

  }
  }

  for (let n of
  fibonacci) {

  if (n >
  100)

  break;

  console.log(n);
  }

```

可以看到，星号(`*`)现在在`[Symbol.iterator]` 定义上。还要注意对象析构的用法。这使得代码更加简洁明了。

以下是前面代码的输出:

代码清单 150

```js
  1
  2
  3
  5
  8
  13
  21
  34
  55
  89

```

让我们考虑另一个例子，在没有迭代器的情况下直接使用生成器:

代码清单 151

```js
  function* range (start, end, step) {

  while (start < end) {

  yield start;

  start += step;

  }
  }

  for (let i of
  range(0, 10,
  2)) {

  console.log(i);
  }

```

这里，我们定义了一个以`start`、`end`和`step`为参数的生成器。当我们到达`yield`时，我们暂停。接下来，我们以`step`的量执行`start`加法。这一直持续到我们到达`end`。

以下是前面代码的输出:

代码清单 152

```js
  0
  2
  4
  6
  8

```

可以使用生成器，并通过数组匹配支持析构。让我们看看下面的例子:

代码清单 153

```js
  let fibonacci = function*
  (numbers) {

  let pre =
  0, cur =
  1;

  while (numbers-- > 0) {

  [ pre, cur ] = [ cur, pre + cur ];

  yield cur;

  }
  }

  for (let n of
  fibonacci(5))

  console.log(n);

  let numbers = [
  ...fibonacci(5) ];
  console.log(numbers);;

  let [ n1, n2, n3, ...others ] =
  fibonacci(5);
  console.log(others[0]);

```

在前面的代码中，我们首先创建了一个生成器。接下来，我们对生成器执行简单的迭代，进行五次迭代。接下来，我们使用`spread`运算符为数字数组赋值。最后，我们在数组中使用模式匹配，并将生成器函数中的值分配给其他人。

下面是前面代码的输出:

代码清单 154

```js
  1
  2
  3
  5
  8
  [ 1, 2, 3, 5, 8 ]
  5

```

发电机的承诺之一是控制流量。这在处理异步编程时变得很重要。我们从承诺中看到了很多，我们将在另一章中看到。让我们看看如何在使用生成器时处理控制流。看看下面的例子:

代码清单 155

```js
  function async (proc, ...params) {

  var iterator = proc(...params);

  return new
  Promise((resolve, reject) => {

  let loop =
  (value) => {

  let result;

  try {

  result = iterator.next(value);

  }

  catch (err) {

  reject(err);

  }

  if (result.done) {

  resolve(result.value);

  }

  else if
  (typeof result.value === "object"

  && typeof
  result.value.then === "function")

  result.value.then((value) => {

  loop(value);

  }, (err) => {

  reject(err);

  })

  else {

  loop(result.value);        

  }

  };

  loop();

  })
  }

  //  application-specific asynchronous builder
  function makeAsync (text, after) {

  return new
  Promise((resolve, reject) => {

  setTimeout(() => resolve(text), after);

  })
  }

  //  application-specific asynchronous procedure
  async(function*
  (greeting) {

  let foo =
  yield makeAsync("foo", 300);

  let bar =
  yield makeAsync("bar", 200);

  let baz =
  yield makeAsync("baz", 100);

  return `${greeting} ${foo} ${bar} ${baz}`;
  },
  "Hello").then((msg) => {

  console.log(msg);
  });

```

好吧，让我们把它分解一下。第一个函数`async` `,`接收一个生成器作为第一个参数以及随后的任何其他参数。在这种情况下，它接收`Hello`。一个新的承诺被创造出来，它将贯穿所有的迭代。在这种情况下，它将正好循环四次。第一次，值是未定义的，我们在传入值时调用`iterator.next()`。所有后续时间，数值代表`foo`、`bar`和`baz`。一旦我们用尽了所有的迭代，我们就完成了循环。

下一个函数`makeAsync`，只是取一些文本，等待一段设定的时间。它使用`setTimeout`功能来模拟真实世界的场景。

最后一个代码段是我们对`async`函数的执行。我们传入一个生成器函数，该函数接受一个问候语作为参数，并包含三个`yield`语句和一个`return`。它调用`async`并执行迭代，同时遵守各种`setTimout`规范。这里好的一点是，不管每次迭代何时调用`return`，我们仍然有看起来非常同步的代码。在迭代完成之前，我们不会显示我们的消息。

下面是前面代码的输出:

代码清单 156

```js
  Hello foo bar baz

```

生成器方法支持类中的方法和基于生成器函数的对象。考虑以下示例:

代码清单 157

```js
  class GeneratorClass {

  * sampleFunc() {

  console.log('First');

  yield;

  console.log('Second'); 

  }
  }
  let gc = new GeneratorClass();
  let gen =
  gc.sampleFunc();
  console.log(gen.next());
  console.log(gen.next());

```

就像我们的第一个例子一样，我们现在在 ES6 类中使用生成器。同样像第一个例子，我们得到相同的输出:

代码清单 158

```js
  First
  { value: undefined, done: false }
  Second
  { value: undefined, done: true }

```