Skip to content

Latest commit

 

History

History
405 lines (253 loc) · 7.24 KB

11.md

File metadata and controls

405 lines (253 loc) · 7.24 KB

十一、生成器

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

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

代码清单 145

  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

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

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

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

代码清单 147

  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

  1
  2
  3
  5
  8
  13
  21
  34
  55
  89

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

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

代码清单 149

  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

  1
  2
  3
  5
  8
  13
  21
  34
  55
  89

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

代码清单 151

  function* range (start, end, step) {

  while (start < end) {

  yield start;

  start += step;

  }
  }

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

  console.log(i);
  }

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

以下是前面代码的输出:

代码清单 152

  0
  2
  4
  6
  8

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

代码清单 153

  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

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

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

代码清单 155

  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()。所有后续时间,数值代表foobarbaz。一旦我们用尽了所有的迭代,我们就完成了循环。

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

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

下面是前面代码的输出:

代码清单 156

  Hello foo bar baz

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

代码清单 157

  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

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