js Generator函数

Generator函数是ES6提供的一种异步编程解决方案。语法行为与传统函数完全不同。Generator函数可以被理解为一种状态机,封装了多个内部状态。执行Generator函数会返回一个遍历器对象,可以一次遍历Generator函数内部的每一个状态。

Generator函数是一个普通函数,不同的是:

  • function关键字与函数名之间有一个星号(*)
  • 函数体内部使用yield表达式,定义不同的状态(yield的意思为“产生”)

Generator函数的调用方式与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用Generator函数后,函数并不执行,返回的也不是函数的运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator)。

说到遍历器对象(Iterator),那就可以想到next()方法。对于遍历器对象,必须调用next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。

Generator函数是分段执行的,yield表达式是暂停执行的标记,next方法可以恢复执行。

例如,创建一个Generator方法:

1
2
3
4
5
function * myGenerator() {
yield 1;
yield 2;
yield 3;
}

获得迭代器对象:

1
const g = myGenerator();

调用next方法获得值:

1
2
3
4
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 3, done: false }
console.log(g.next()); // { value: undefined, done: true }

通过for...of遍历迭代器:

1
2
3
4
for (let i of g) {
console.log(i);
}
// 结果:1, 2, 3

yield表达式

在Generator函数返回的遍历器对象,只有next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数,yield表达式就是暂停标志。遍历器对象的next方法的运行逻辑:

  1. 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回对象的value属性值。
  2. 下一次调用next方法,再继续往下执行,直到遇到下一个yield表达式。
  3. 如果没有再遇到新的yield表达式,就一直运行到函数的结束,直到return语句为止,并将return语句后面的表达式的值作为返回对象的value属性值
  4. 如果该函数没有return语句,则返回对象的value属性值为undefined

注意:yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行。

Generator函数也可以不用yield表达式,这时就编程了一个单纯的暂缓执行函数;另外,yield表达式只能在Generator函数里面使用,用在其他地方都会报错。

next方法的参数

yield表达式本身没有返回值,或者说总是返回undefinednext方法可以带一个参数,该参数会被当做上一个yield表达式的返回值:

1
2
3
4
5
6
7
8
9
10
11
12
function * myGenerator() {
for (let i = 0; true; i++) {
const reset = yield i;
if(reset) {
i = -1;
}
}
}
const g = myGenerator();
console.log(g.next()); // { value: 0, done: false }
console.log(g.next()); // { value: 1, done: false }
console.log(g.next(true)); // { value: 0, done: false }

调用第二个next的时候,i的值为1,此时Generator函数执行暂停在yield这里,调用第三个next的时候,给next传入值为true,等于上一个yield i表达式返回的值为true,这时候程序继续执行循环,先将i重置为-1,然后执行自增,运行到yield的时候i的值为1.

Generator。prototype.thrwo()

Generator函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function * myGenerator() {
try {
for (let i = 0; true; i++) {
const reset = yield i;
if(reset) {
i = -1;
}
}
} catch (e){
console.log('内部捕获', e);
}
}
const g = myGenerator();
try {
console.log(g.next());
console.log(g.next());
console.log(g.throw('a'));
console.log(g.next());
console.log(g.throw('b'));
} catch (e) {
console.log('外部捕获', e);
}
/**
* 输出:
* { value: 0, done: false }
* { value: 1, done: false }
* 内部捕获 a
* { value: undefined, done: true }
* { value: undefined, done: true }
* 外部捕获 b
* /

遍历器对象连续抛出两个错误,第一个错误被Generator函数体内的catch语句捕获,第二次抛出错误的时候,由于Generator函数体内部的catch语句已经执行过了,不会再捕捉这个错误,所以这个错误被抛出了Generator函数体,被函数体外的catch语句捕获。

注意:throw方法抛出的错误要被内部捕获,前提是必须至少执行过一次next方法。

一旦Generator执行过程中抛出错误,就不会再执行下去了。如果此后还调用next方法,将返回一个{value: undefined, done: ture}对象。即JavaScript引擎认为这个Generator已经运行结束了。

Generator.prototype.return()

Generator函数的遍历器对象中还有一个return方法,可以返回给定的值,并且终结遍历Generator函数。例如:(Generator还使用上面的)

1
2
3
4
const g = myGenerator();
console.log(g.next()); // { value: 0, done: false }
console.log(g.return(3)); // { value: 3, done: true }
console.log(g.next()); // { value: undefined, done: true }

如果return方法调用时不提供参数,则返回的valueundefined.


ES6基础语法是用到什么就去深入了解下,在用的过程中加深理解。如果用不到只是去看文档的话,一个是看不进去,一个是看完了也很快就忘记了。这样在用的过程中去看文档同时进行笔记记录,会理解更深,也更难被忘记。

这个笔记只是学习了简单的使用,看文档上有很多复杂的使用,一般也用不上,如果真的用到了,再来补全。

参考文档:http://es6.ruanyifeng.com/#docs/generator