ES6 - Note7:Generator函数

旧时模样 提交于 2019-12-29 17:56:50

Generator函数

1.Generator函数是ES6增加的异步编程解决方案之一,与普通的函数行为完全不同,类似于一个状态机,内部封装了多个状态。

在函数定义的形式上,跟普通函数差不多,有两处不同,一是function关键字与函数名之间需要一个星号(*),二是函数内部使用yield语句定义各种状态,且yield只能用在Generator函数中,否则报错,如下所示

function* testGenerator(){//星号只要在function与函数名之间就可
    yield 'test';
    yield 'generator';
    return '!';
}

function testYield(){
    yield 'hello';//报错,但在ff浏览器中不会报错,被自动认为是Generator函数
}
VM1211:9 Uncaught SyntaxError: Unexpected string(…)

调用Generator函数,该函数不会立即执行,而是返回一个遍历器Iterator,必须调用该遍历器的next方法去遍历函数内部的下一个状态,如下所示

function* generator(){
    console.log('hehe');
    yield 'hello';
    yield 'ecmascript';
    return 'end';
}
var gen = generator();
gen.next();
hehe
Object { value: "hello", done: false }
gen.next()
Object { value: "ecmascript", done: false }
gen.next()
Object { value: "end", done: true }
gen.next()
Object { value: undefined, done: true }

当然也可以使用for..of或者扩展运算符遍历,但不会遍历到return返回值,如下所示

for(let x of generator()){
    console.log(x);
}
hehe
hello
ecmascript
[...generator()].forEach((val,idx,arr)=>console.log(val));
hehe
hello
ecmascript

Generator函数中yield语句是暂停标志,可以不存在该语句,这时函数可以当作是暂缓执行函数,如下所示

function* genfunc(){
    console.log("稍后执行...");
}

var g = genfunc();

setTimeout(() => g.next(),2000);
3
稍后执行...

 由于yield语句只能用在Generator函数中,因此在使用回调函数时需要特别的注意,比如在Generator函数使用数组的map,forEach方法时,不能在函数参数里面写yield语句,如下所示

function* arrGene(arr){
    arr.forEach(function(val,idx,arr){
        yield val;
    });
}
console.log([...arrGene([1,2,3])]);
VM142:4 Uncaught SyntaxError: Unexpected identifier(…)
-------------------------使用for循环代替--------------------------
function* arrGene(arr){
    for(let i=0,len=arr.length; i<len; i++){
        yield arr[i];
    }
}
console.log([...arrGene([1,2,3])]);
VM179:8 [1, 2, 3]

Generator是一个遍历器生成器,因此可以赋值给没有默认遍历器的对象的Symbol.iterator属性,让该对象能够使用for...of语句,如下所示

var obj = {};
obj[Symbol.iterator] = function* (){
    yield 'hello';
    yield 'world';
    return '!';
}
for(let x of obj){
    console.log(x);
}
hello
world

2.next方法参数

Generator实例的next方法可以传递参数,作为该实例内部上一个yield语句的返回值,如不通过next方法传值,yield语句的返回值总是undefined,如下所示

//不传值的情况
function* generator(){
    console.log('hello generator...');
    let v = yield 'ni';
    let u = yield v+'test';
    return u+v+'';
}
var f = generator()
f.next()
hello generator...
Object { value: "ni", done: false }
f.next()
Object { value: "undefinedtest", done: false }
f.next()
Object { value: "NaN", done: true }
//传值的情况
var z = generator();
z.next();
hello generator...
Object { value: "ni", done: false }
z.next('frist');
Object { value: "fristtest", done: false }
z.next('second');
Object { value: "secondfrist", done: true }

因此我们利用这一特性来向generator函数内部注入值来控制函数的执行,如下所示

unction* gene(){
    console.log('start generating...');
    let ret = yield 'hello';
    if(ret == 'a'){
        yield 'a';
    }else{
        yield 'b';
    }
    return 'ending';
}
var g = gene();
g.next()
start generating...
Object { value: "hello", done: false }
g.next('c');
Object { value: "b", done: false }
g.next();
Object { value: "ending", done: true }

3.Generator实例方法throw

throw方法可以在函数体外抛出错误,然后在generator函数内部捕获错误,但同时只能一条错误异常,如下所示

function* catchGene(){
    try{
        yield 'try';
    }catch(e){
        console.log('generator函数内部捕获:'+e);
    }
}
var g = catchGene();
try{
    console.log(g.next());
    g.throw('a');
    g.throw('b');
}catch(e){
    console.log('全局捕获:'+e);
}
Object { value: "try", done: false }
generator函数内部捕获:a
全局捕获:b

如果在generator函数体内没有部署try...catch语句,则generator实例throw抛出的错误不能被捕获,可以被全局catch捕获,如下所示

function* gen(){
    yield 'hello';
    yield 'world';
}
var g = gen();
try{
    g.throw('a');
}catch(e){
    console.log('全局捕获:'+e);
}
全局捕获:a

不管是generator实例throw方法或者throw命令抛出的错误,只要被捕获了就不会影响generator函数的next方法的执行,否则遍历直接终止,如下所示

function* gen(){
    yield 'hello';
    yield 'world';
}
var g = gen();
console.log(g.next());
g.throw();
console.log(g.next());
VM226:7 Object {value: "hello", done: false}
VM226:8 Uncaught undefined
-----------------------使用try...catch捕获-----------------
function* gen(){
    try{
        yield 'hello';
        
    }catch(e){
        console.log(e);
    }
    yield 'world';
    yield 'ending';
}
var g = gen();
console.log(g.next());
console.log(g.throw('a'));
console.log(g.next());
Object { value: "hello", done: false }
a
Object { value: "world", done: false }
Object { value: "ending", done: false }

特别注意的是catch捕获到错误后,继续执行到下一个yield语句,相当于再执行了一个next方法。

generator函数内部抛出的错误,可以被函数体外的catch捕获,这时由于报错,JS引擎认为generator函数遍历完毕,之后再调用next都是返回{value:undefined,done:true}对象,如下所示

function* gen(){
    yield 'hello';
    yield x+y;
    yield 'world';
}
var g = gen();
console.log(g.next());
try{
    console.log(g.next());
}catch(e){
    console.log(e);
}
console.log(g.next());
Object { value: "hello", done: false }
ReferenceError: x is not defined
堆栈跟踪:
gen@debugger eval code:3:2
@debugger eval code:9:14

Object { value: undefined, done: true }

4.Generator实例方法return

该方法会返回给定的值,并终止generator函数的遍历,如下所示

function* gen(){
    yield 'hello';
    yield 'world';
}
var g = gen();
g.next()
Object { value: "hello", done: false }
g.return("return");
Object { value: "return", done: true }
g.next()
Object { value: undefined, done: true }

如果return方法没有给出任何值,则返回undefined,如果generator函数体内部部署了try...finally语句,return语句会被推迟到finally执行完后执行,如下所示

function* gen(){
    try{
        yield 'hello';
    }finally{
        yield 'world';
    }
}
var g = gen();
g.next()
Object { value: "hello", done: false }
g.return("nihao")
Object { value: "world", done: false }
g.next()
Object { value: "nihao", done: true }
g.next()
Object { value: undefined, done: true }

5.yield*语句

yield*语句用在generator函数内部执行另一个遍历器对象,如下所示

function* letter(){
    yield 'b';
    yield 'c';
    yield 'd';
}
function* gen(){
    yield "a";
    letter(); //直接调用没有效果
    yield "e";
}
[...gen()]
Array [ "a", "e" ]
------------------------------------------
function* letter(){
    yield 'b';
    yield 'c';
    yield 'd';
}
function* gen(){
    yield "a";
    yield* letter();//yield* 语句
    yield "e";
}
[...gen()]
Array [ "a", "b", "c", "d", "e" ]

只要实现了Iterator接口的对象都可以使用yield*遍历,如下所示

function* gen(){
    yield 1;
    yield 2;
    yield* [3,4,5,6,7];
    yield 10;
}
console.log([...gen()]);
Array [ 1, 2, 3, 4, 5, 6, 7, 10 ]
----------------------------遍历嵌套函数-----------------------
function* walkArr(arr){
    if(Array.isArray(arr)){
        for(let v of arr){
            yield* walkArr(v);
        }
    }else{
        yield arr;
    }
}
var w = walkArr([1,[2,[3,10,[9]]]]);
[...w];
Array [ 1, 2, 3, 10, 9 ]

Generator函数就介绍到此咯

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!