Js的Generator函数(二)

18 篇文章 0 订阅

Generator 组合

Generator 组合(composition)是 generator 的一个特殊功能,它允许透明地(transparently)将 generator 彼此“嵌入(embed)”到一起。

例如,我们有一个生成数字序列的函数:

function* generateSequence(start, end) {
  for (let i = start; i <= end; i++) yield i;
}

现在,我们想重用它来生成一个更复杂的序列:

  • 首先是数字 0..9(字符代码为 48…57),
  • 接下来是大写字母 A..Z(字符代码为 65…90)
  • 接下来是小写字母 a...z(字符代码为 97…122)

我们可以对这个序列进行应用,例如,我们可以从这个序列中选择字符来创建密码(也可以添加语法字符),但让我们先生成它。

在常规函数中,要合并其他多个函数的结果,我们需要调用它们,存储它们的结果,最后再将它们合并到一起。

对于 generator 而言,我们可以使用 yield* 这个特殊的语法来将一个 generator “嵌入”(组合)到另一个 generator 中:

组合的 generator 的例子:

function* generateSequence(start, end) {
  for (let i = start; i <= end; i++) yield i;
}

function* generatePasswordCodes() {

// 0..9
  yield* generateSequence(48, 57);

  // A..Z
  yield* generateSequence(65, 90);

  // a..z
  yield* generateSequence(97, 122);

}

let str = '';

for(let code of generatePasswordCodes()) {
  str += String.fromCharCode(code);
}

alert(str); // 0..9A..Za..z

yield* 指令将执行 委托 给另一个 generator。这个术语意味着 yield* gen 在 generator gen 上进行迭代,并将其产出(yield)的值透明地(transparently)转发到外部。就好像这些值就是由外部的 generator yield 的一样。

执行结果与我们内联嵌套 generator 中的代码获得的结果相同:

function* generateSequence(start, end) {
  for (let i = start; i <= end; i++) yield i;
}

function* generateAlphaNum() {

// yield* generateSequence(48, 57);
  for (let i = 48; i <= 57; i++) yield i;

  // yield* generateSequence(65, 90);
  for (let i = 65; i <= 90; i++) yield i;

  // yield* generateSequence(97, 122);
  for (let i = 97; i <= 122; i++) yield i;

}

let str = '';

for(let code of generateAlphaNum()) {
  str += String.fromCharCode(code);
}

alert(str); // 0..9A..Za..z

Generator 组合(composition)是将一个 generator 流插入到另一个 generator 流的自然的方式。它不需要使用额外的内存来存储中间结果。


“yield” 是一条双向路

目前看来,generator 和可迭代对象类似,都具有用来生成值的特殊语法。但实际上,generator 更加强大且灵活。

这是因为 yield 是一条双向路(two-way street):它不仅可以向外返回结果,而且还可以将外部的值传递到 generator 内。

调用 generator.next(arg),我们就能将参数 arg 传递到 generator 内部。这个 arg 参数会变成 yield 的结果。

我们来看一个例子:

function* gen() {
// 向外部代码传递一个问题并等待答案
  let result = yield "2 + 2 = ?"; // (*)

  alert(result);
}

let generator = gen();

let question = generator.next().value; // <-- yield 返回的 value

generator.next(4); // --> 将结果传递到 generator 中

image.png

  1. 第一次调用 generator.next() 应该是不带参数的(如果带参数,那么该参数会被忽略)。它开始执行并返回第一个 yield "2 + 2 = ?" 的结果。此时,generator 执行暂停,而停留在 (*) 行上。
  2. 然后,正如上面图片中显示的那样,yield 的结果进入调用代码中的 question 变量。
  3. generator.next(4),generator 恢复执行,并获得了 4 作为结果:let result = 4

请注意,外部代码不必立即调用 next(4)。外部代码可能需要一些时间。这没问题:generator 将等待它。

例如:

// 一段时间后恢复 generator
setTimeout(() => generator.next(4), 1000);

我们可以看到,与常规函数不同,generator 和调用 generator 的代码可以通过在 next/yield 中传递值来交换结果。

为了讲得更浅显易懂,我们来看另一个例子,其中包含了许多调用:

function* gen() {
  let ask1 = yield "2 + 2 = ?";

  alert(ask1); // 4

  let ask2 = yield "3 * 3 = ?"

  alert(ask2); // 9
}

let generator = gen();

alert( generator.next().value ); // "2 + 2 = ?"

alert( generator.next(4).value ); // "3 * 3 = ?"

alert( generator.next(9).done ); // true

执行图:

image.png

  1. 第一个 .next() 启动了 generator 的执行……执行到达第一个 yield
  2. 结果被返回到外部代码中。
  3. 第二个 .next(4)4 作为第一个 yield 的结果传递回 generator 并恢复 generator 的执行。
  4. ……执行到达第二个 yield,它变成了 generator 调用的结果。
  5. 第三个 next(9)9 作为第二个 yield 的结果传入 generator 并恢复 generator 的执行,执行现在到达了函数的最底部,所以返回 done: true

这个过程就像“乒乓球”游戏。每个 next(value)(除了第一个)传递一个值到 generator 中,该值变成了当前 yield 的结果,然后获取下一个 yield 的结果。


generator.throw

正如我们在上面的例子中观察到的那样,外部代码可能会将一个值传递到 generator,作为 yield 的结果。

……但是它也可以在那里发起(抛出)一个 error。这很自然,因为 error 本身也是一种结果。

要向 yield 传递一个 error,我们应该调用 generator.throw(err)。在这种情况下,err 将被抛到对应的 yield 所在的那一行。

例如,"2 + 2?" 的 yield 导致了一个 error:

function* gen() {
  try {
    let result = yield "2 + 2 = ?"; // (1)

    alert("The execution does not reach here, because the exception is thrown above");
  } catch(e) {
    alert(e); // 显示这个 error
  }
}

let generator = gen();

let question = generator.next().value;

generator.throw(new Error("The answer is not found in my database")); // (2) 

在第二行引入到 generator 的 error 导致了在第一行中的 yield 出现了一个异常。在上面这个例子中,try..catch 捕获并显示了这个 error。

如果我们没有捕获它,那么就会像其他的异常一样,它将从 generator “掉出”到调用代码中。

调用代码的当前行是 generator.throw 所在的那一行,标记为 (2)。所以我们可以在这里捕获它,就像这样:

function* generate() {
  let result = yield "2 + 2 = ?"; // 这行出现 error
}

let generator = generate();

let question = generator.next().value;

try {
  generator.throw(new Error("The answer is not found in my database"));
} catch(e) {
  alert(e); // 显示这个 error
} 

如果我们没有在那里捕获这个 error,那么,通常,它会掉入外部调用代码(如果有),如果在外部也没有被捕获,则会杀死脚本。


总结

  • Generator 是通过 generator 函数 function* f(…) {…} 创建的。
  • 在 generator(仅在)内部,存在 yield 操作。
  • 外部代码和 generator 可能会通过 next/yield 调用交换结果。

在现代 JavaScript 中,generator 很少被使用。但有时它们会派上用场,因为函数在执行过程中与调用代码交换数据的能力是非常独特的。而且,当然,它们非常适合创建可迭代对象。


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个前端人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值