ES6 - Note7:Generator函數

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方法去遍歷函數內部的下一個狀態,以下所示ecmascript

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語句是暫停標誌,能夠不存在該語句,這時函數能夠看成是暫緩執行函數,以下所示ide

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方法參數spa

Generator實例的next方法能夠傳遞參數,做爲該實例內部上一個yield語句的返回值,如不經過next方法傳值,yield語句的返回值老是undefined,以下所示debug

//不傳值的狀況
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函數就介紹到此咯

相關文章
相關標籤/搜索