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函數就介紹到此咯