Generator 生成器
# 函数组成
- 一是在 function 后面,函数名之前有个 *
- 函数内部有 yield 表达式。
function* func(){
console.log("one");
yield '1';
console.log("two");
yield '2';
console.log("three");
return '3';
}
# 执行机制
调用 Generator 函数和调用普通函数一样,在函数名后面加上()即可,但是 Generator 函数不会像普通函数一样立即执行,而是返回一个指向内部状态对象的指针,所以要调用遍历器对象 Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行。
var f = func();
f.next();
// one
// {value: "1", done: false}
f.next();
// two
// {value: "2", done: false}
f.next();
// three
// {value: "3", done: true}
f.next();
// {value: undefined, done: true}
第一次调用 next 方法时,从 Generator 函数的头部开始执行,先是打印了 one ,执行到 yield 就停下来,并将 yield 后边表达式的值 '1',作为返回对象的 value 属性值,此时函数还没有执行完, 返回对象的 done 属性值是 false 。
第二次调用 next 方法时,同上步 。
第三次调用 next 方法时,先是打印了 three ,然后执行了函数的返回操作,并将 return 后面的表达式的值,作为返回对象的 value 属性值,此时函数已经结束,多以 done 属性值为 true 。
第四次调用 next 方法时, 此时函数已经执行完了,所以返回 value 属性值是 undefined ,done 属性值是 true 。如果执行第三步时,没有 return 语句的话,就直接返回 {value: undefined, done: true}。
# 函数返回的遍历器对象的方法
# next 方法
next 方法不传入参数的时候, yield 表达式的返回值是 undefined 。当 next 传入参数的时候,该参数会作为上一步yield的返回值。
function* sendParameter() {
console.log('start');
const x = yield '2';
console.log('one:' + x);
const y = yield '3';
console.log('two:' + y);
console.log('total:' + (x + y));
}
next 不传参
const sendp1 = sendParameter();
console.log(sendp1.next());
// start
// {value: "2", done: false}
console.log(sendp1.next());
// one:undefined
// {value: "3", done: false}
console.log(sendp1.next());
// two:undefined
// total:NaN
// {value: undefined, done: true}
next 传参
const sendp2 = sendParameter();
console.log(sendp2.next(10););
// start
// {value: "2", done: false}
console.log(sendp2.next(20););
// one:20
// {value: "3", done: false}
console.log(sendp2.next(30););
// two:30
// total:50
// {value: undefined, done: true}
分析一下 next 传参的情况,第一次调用 sendp2.next 返回 2,
第二次调用 sendp2.next 将上一次 yield 表达式的值设为 20,即 one 等于 20
第三次调用 sendp2.next 将上一次 yield 表达式的值设为 30,即 two 等于 30
那么 total 等于 50 ;
遍历器对象的 next 方法的运行逻辑如下。
(1)遇到 yield 表达式,就暂停执行后面的操作,并将紧跟在 yield 后面的那个表达式的值,作为返回的对象的 value 属性值。
(2)下一次调用 next 方法时,再继续往下执行,直到遇到下一个 yield 表达式。
(3)如果没有再遇到新的 yield 表达式,就一直运行到函数结束,直到 return 语句为止,并将 return 语句后面的表达式的值,作为返回的对象的 value 属性值。
(4)如果该函数没有 return 语句,则返回的对象的 value 属性值为 undefined。
# return 方法
return 方法返回给定值,并结束遍历 Generator 函数。
return 方法提供参数时,返回该参数;不提供参数时,返回 undefined 。
function* foo(x) {
var y = 2 * (yield x + 1);
var z = yield y / 3;
return x + y + z;
console.log('打印不出来'])
}
var b = foo(5);
console.log(b.next()); // { value:6, done:false }
console.log(b.next(12)); // { value:8, done:false }
console.log(b.next(13));// { value: 42, done: true }
console.log(b.next());// { value: undefined, done: true }
第一次调用b的next方法时,返回x+1的值 6;
第二次调用next方法,将上一次 yield 表达式的值设为 12,因此 y 等于 24,返回y / 3的值 8;
第三次调用next方法,将上一次 yield 表达式的值设为 13,因此 z 等于 13,这时x等于 5,y等于 24,
所以return语句的值等于 42。
return 之后就结束了该方法,因此最后一个 console.log('打印不出来') 并没有执行。
function* foo() {
yield 1;
yield 2;
yield 3;
}
var f = foo();
f.next();
// {value: 1, done: false}
f.return('foo');
// {value: "foo", done: true}
f.next();
// {value: undefined, done: true}
# throw 方法
throw 方法可以再 Generator 函数体外面抛出异常,再函数体内部捕获
var g = function* () {
try {
yield;
} catch (e) {
console.log('catch inner', e);
}
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('catch outside', e);
}
// catch inner a
// catch outside b
遍历器对象抛出了两个错误,第一个被 Generator 函数内部捕获,第二个因为函数体内部的 catch 函数已经执行过了,不会再捕获这个错误,所以这个错误就抛出 Generator 函数体,被函数体外的 catch 捕获。
# yield* 表达式
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let v of bar()) {
console.log(v);
}
// "x"
// "a"
// "b"
// "y"
foo 和 bar 都是 Generator 函数,在 bar 里面调用 foo ,就需要手动遍历 foo 。如果有多个 Generator 函数嵌套,写起来就非常麻烦。
因此有了 yield* 表达式。
function* gen() {
yield* ['a', 'b', 'c'];
}
gen().next(); // { value:"a", done:false }
var a = gen();
a.next();
// {value: 'a', done: false}
a.next();
// {value: 'b', done: false}
a.next();
// {value: 'c', done: false}
a.next();
// {value: undefined, done: true}
yield 命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器对象。
function* gen() {
yield ['a', 'b', 'c'];
}
gen().next(); // {value: ['a', 'b', 'c'], done: false}
var a = gen();
a.next();
// {value: ['a', 'b', 'c'], done: false}
a.next();
// {value: undefined, done: true}
# 使用
# Generator 函数的数据交换
next 返回值的 value 属性,是 Generator 函数向外输出数据;
通过 next 传参的方法,改变返回值,向 Generator 函数体内输入数据。
# 协程
yield 命令是异步两个阶段的分界线,它表示执行到此处,执行权将交给其他协程。
协程遇到 yield 命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。代码的写法非常像同步操作。
# 错误处理
throw 方法抛出的错误,可以被函数体内的try...catch代码块捕获。
参考链接:
- https://www.runoob.com/w3cnote/es6-generator.html
- https://es6.ruanyifeng.com/#docs/generator