ES6 Generator函数
Generator函数概念
Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。
对于Generator函数是有多重理解角度。从语法上,首先可以把它理解成一个状态机,封装了很多个内部状态。
执行
Generator函数会返回一个遍历器对象,可以一次遍历Generator函数内部的每一个状态。
Generator函数特征
- function命令与函数名之间有一个星号(
*);- 函数体内部使用
yield语句定义不同的内部状态(yield是产出的意思);- 函数返回的是一个遍历器对象,须调用
next方法才会一次返回不同状态的值。
function *helloWorldGenerator(){
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
上面代码就定义了一个Generator函数--helloWorldGenerator,它内部有两个yield语句,和一个return语句,即该函数有三个状态:hello、 world、和 return语句(结束执行);
与其他不同函数不同的是,Generator函数调用之后,该函数并不会执行,返回也不是函数的运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象Iterator,在之后,我们必须调用遍历器对象的next方法,使得指正指向下一个状态,每次调用next方法,内部指针就从函数的头部或者上一次停下来的地方开始执行,直到遇到下一条yield语句,或者return语句为止。
hw.next();
//{value: "hello", done: false}
hw.next();
//{value: "world", done: false}
hw.next();
//{value: "ending", done: true}
hw.next();
//{value: undefined, done: true}
上面一共执行了四次next方法;
yield语句
由于Generator函数返回的遍历器对象只有调用next方法才会遍历下一个内部状态,所以提供了一种可以暂停执行的函数。yield语句就是暂停标志。
yield语句,与next方法的运行逻辑
- 遇到
yield语句就暂停执行后面的操作,并将紧跟在yield后的表达式的值作为返回的对象的value属性值。- 下一次调用
next方法时再继续往下执行,直到遇到下一条yield语句。- 如果没有遇到
yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值作为返回对象的value属性的值。- 如果该函数没有
return语句,则返回对象的value属性值为undefined,对象的done属性值为true,表示遍历已经结束。
-
yield语句和return语句的区别。
yield语句和return语句的相似之处在于都能返回紧跟在语句后的表达式的值。区别在于每次遇到yield语句函数会暂停执行,下一次会从该位置继续向后执行,而人团语句不具备位置记忆的功能,一个函数里只能执行一次(或者说一条)return语句,但可以执行多次yield语句。
-
Generator函数不用yield语句。
这时候就变成了一个单纯的暂缓执行函数。
function *noyield(){
console.log('执行了!')
}
var generator = noyield();
generator.next();
//执行了!
- 普通函数中不能使用
yield语句
function fn(){
yield 1;
}
fn() // SyntaxError: Unexpected number
for...of中使用Generator函数
for...of会自动遍历Generator函数,且不需要再调用 next方法.
function *foo(){
yield 1;
yield 2;
yield 3;
yield 4;
return 5;
}
for(let i of foo()){
console.log(i)
} //1 2 3 4
上面的代码使用
for...of循环4条yield语句的值,一旦next方法的返回对象的done属性为true,for...of循环就会终止,且不包含改返回对象,所以上面的return语句返回的6不包括在for...of循环中。
for...of语句,扩展运算符(···)、解构赋值和Array.from方法内部调用的都是遍历器接口。这意味着它们可以将Generator函数返回的Iterator对象作为参数。
function *numbers(){
yield 1;
yield 2;
return 3;
yield 4;
}
//扩展运算符
[...numbers()]//[ 1, 2 ];
//Array.from
Array.from(numbers()) //[ 1,2 ]
//解构赋值
let [ x, y ] = numbers()
x //1
y //2
//for...of
for (let i of numbers()){
console.log(i)
}
//1
//2
Generator.prototype.throw();
Generator函数返回的遍历器都有一个throw方法,可以在函数体外抛出错误,然后在体内捕获。
var g = function *(){
while(true){
try{
yield;
}catch(e){
if(e!='a') throw e
console.log('内部捕获',e)
}
}
}
var i = g();
i.next();
try{
i.throw('a');
i.throw('b');
}catch(e){
console.log('外部捕获',e);
}
//内部捕获 a
//外部捕获 b
上面代码中,遍历器对象i连续抛出错误。第一个错误被Generator函数体内catch捕获,然后Generator函数执行完成,于是第二个错误被函数体外的catch语句捕获。
Generator.prototype.return();
-
Generator函数返回的遍历器对象还有一个return方法,可以返回给定的值,并终结Generator函数的遍历。
function *numbers(){
yield 1;
yield 2;
return 3;
yield 4;
}
var num = numbers();
num.next();
// {value: 1, done: false}
num.return('stop');
//{value: "stop", done: true}
num.next();
//{value: undefined, done: true}
上面函数在遍历器
num调用return方法后,遍历终止,返回值的done为true,之后调用next方法总是会返回true
- 如果
Generator函数内部有try...finally代码块,namereturn方法会推迟到finally代码块执行完再执行。
function *numbers(){
try{
yield 1;
yield 2;
}finally{
yield 3;
yield 4;
}
yield 5;
};
var num = numbers();
num.next();
//{value: 1, done: false}
num.next();
//{value: 2, done: false}
num.return();
//{value: 3, done: false}
num.next();
//{{value: 4, done: false}
num.next();
//{value: 6, done: true}
总结
Generator函数是分段执行的,yield语句是暂停执行的标记,而next方法可以恢复执行。Generator函数它返回一个遍历器对象,代表Generator函数的内部指针。Generator函数它可以返回一系列的值,因为它可以有人以多条yield语句。yield语句是Generator函数特有的(也仅限于它),将紧跟在yield后的表达式的值作为返回的对象的value属性值。- 使用
for...of循环运行的Generator函数,不需要调用next方法,在next的返回对象done属性为true时,for...of循环终止。(扩展运算符,Array.from,解构赋值,都可以使用Generator函数返回的Iterator对象作为参数)