详解 ECMAScript 6 中的生成器(3)生成器的高级用法
- UID
- 1066743
|
详解 ECMAScript 6 中的生成器(3)生成器的高级用法
生成器的高级用法在介绍完生成器的基本用法之后,下面介绍生成器的一些高级用法。
生成器对象的 return 方法生成器对象的 return 方法可以用来返回给定值并结束它的执行。其使用效果类似于在生成器函数中使用 return 语句。在代码清单 10 中,调用 func.return('d') 会返回传入的值 d,并结束生成器,也就是 done 的值变为 true,即使生成器中仍然还有值 b 和 c 未被生成。方法 return 可以被多次调用,每次调用都返回传入的值。
清单 10. 生成器对象的 return 方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
| function *values() {
yield 'a';
yield 'b';
yield 'c';
}
let func = values();
func.next();
// -> {value: "a", done: false}
func.return('d');
// -> {value: "d", done: true}
func.next();
// -> {value: undefined, done: true}
|
生成器对象的 throw 方法生成器对象的 throw 方法可以用来传入一个值,并使其抛出异常。throw 和之前提到的 next 都可以传入值到生成器对象中来改变其行为。通过 next 传入的值会作为上一个 yield 表达式的值,而通过 throw 传入的值则相当于把上一个 yield 语句替换到一个 throw 语句。在代码清单 11 中,当 func.throw('hello') 被调用时,上一个 yield 表达式 yield x + 1 被替换成 throw 'hello'。由于抛出的对象没有被处理,会被直接传递到 JavaScript 引擎,导致生成器的执行终止。
清单 11. 生成器对象的 throw 方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| function *sample() {
let x = yield 1;
let y = yield x + 1;
yield y * 10;
}
let func = sample();
func.next();
// -> {value: 1, done: false}
func.next(1);
// -> {value: 2, done: false}
func.throw('hello');
// -> Uncaught hello
func.next();
// -> {value: undefined, done: true}
|
我们可以在生成器函数中使用 try-catch 来捕获异常并处理。代码清单 12 中,在调用 func.throw(new Error('boom!')) 时,上一个 yield 表达式 yield 2 被替换成了 throw new Error('boom!')。抛出的对象由 try-catch 进行了处理,因此生成器的执行可以被继续。
清单 12. 使用 try-catch 捕获异常1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| function *sample() {
yield 1;
try {
yield 2;
} catch (e) {
console.error(e);
}
yield 3;
yield 4;
}
let func = sample();
func.next();
// -> {value: 1, done: false}
func.next();
// -> {value: 2, done: false}
func.throw(new Error('boom!'));
// -> Error: boom!
// -> {value: 3, done: false}
func.next();
// -> {value: 4, done: false}
|
使用 yield * 表达式目前我们看到的生成器对象每次只通过 yield 表达式来产生一个值。实际上,我们可以使用 yield * 表达式来生成一个值的序列。当使用 yield * 时,当前生成器对象的序列生成被代理给另外一个生成器对象或可迭代对象。代码清单 13 中的生成器函数 oneToThree 通过 yield* [1, 2, 3] 来生成 3 个值,与 中的生成器函数 sample 的结果是相同的,不过使用 yield * 的方式更加简洁易懂。
清单 13. 使用 yield * 表达式1
2
3
4
5
6
| function *oneToThree() {
yield* [1, 2, 3];
}
debug(oneToThree());
// -> 输出 1, 2, 3
|
在一个生成器函数中可以使用多个 yield * 表达式。在这种情况下,来自每个 yield * 表达式的值会被依次生成。 代码清单 14 中的生成器 multipleYieldStars 使用了 2 个 yield * 语句和一个 yield 语句。这里需要注意的是字符串 hello 会被当成一个可迭代的对象,也就是会输出其中包含的每个字符。
清单 14. 使用多个 yield * 表达式1
2
3
4
5
6
7
8
| function *multipleYieldStars() {
yield* [1, 2, 3];
yield 'x';
yield* 'hello';
}
debug(multipleYieldStars());
// -> 输出 1, 2, 3, 'x', 'h', 'e', 'l', 'l', 'o'
|
由于 yield * 也是表达式,它是有值的。它的值取决于在 yield * 之后的表达式。yield * 表达式的值是其后面的生成器对象或可迭代对象所产生的最后一个值,也就是属性 done 为 true 时的那个值。如果 yield * 后面是可迭代对象,那么 yield * 表达式的值总是 undefined,这是因为最后一个生成的值总是 {value: undefined, done: true}。如果 yield * 后面是生成器对象,我们可以通过在生成器函数中使用 return 来控制最后一个产生的值。在代码清单 15 中,通过 return 来改变了生成器函数 abc 的返回值,因此 yield *abc() 的值为 d。
清单 15. yield * 表达式的值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| var result;
function loop(iterable) {
for (let value of iterable) {
//ignore
}
}
function *abc() {
yield* 'abc';
return 'd';
}
function *generator() {
result = yield* abc();
}
loop(generator());
console.log(result);
// -> "d"
|
表达式 yield 和 yield * 都可以进行嵌套。在代码清单 16 中,最内层的 yield 表达式生成值 1,然后中间的 yield 表达生成 yield 1 的值,也就是 undefined。这是因为在遍历调用 next 时并没有传入参数。最外层的 yield 的值也是 undefined。
清单 16. 嵌套的 yield 表达式1
2
3
4
5
6
| function *manyYields() {
yield yield yield 1;
}
debug(manyYields());
// 输出 1, undefined, undefined
|
在代码清单 17 中,内层的 yield * 首先产生由生成器 oneToThree 所生成的值 1,2 和 3,然后外层的 yield 再产生 yield * 的值,也就是 undefined。
清单 17. 嵌套的 yield * 和 yield 表达式1
2
3
4
5
6
7
8
9
10
| function *oneToThree() {
yield* [1, 2, 3];
}
function *values() {
yield yield* oneToThree();
}
debug(values());
// -> 输出 1, 2, 3, undefined
|
|
|
|
|
|
|