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函数就介绍到此咯