Generator生成器
什么是生成器?
ES6的新语法,并不直接执行逻辑,而是生成一个对象--迭代器,通过调用这个对象的next()方法,来一步步向后走。执行完一个yield关键字就会停止,等待下一次next()调用。
funtion* myLogin(){ yield 2; yield 5;
return 10; }
var it = myLogin();
it.next(); // 2
it.next(); // 5
当执行到return语句时,迭代器的迭代已经结束。如果生成器中没有return语句,则返回对象的value值为undefined
return 10; // {value: 10, done: true}
yield表达式如果用在另一个表达式中,必须放在圆括号中
console.log('Hello' + (yield 12))
console.log('Hello' + yield 12) //报错
next()的参数问题
next()函数是可以接受一个参数的,其作为上一次yield的返回值。但第一次使用next()函数时,是没有上一次的yield的,并且实际上是来开启遍历器对象的,传参是没有效果的。
在有些情况下,从第二次使用next()开始传入参数就很有必要了
function* foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var a = foo(5); a.next() // Object{value:6, done:false} a.next() // Object{value:NaN, done:false} a.next() // Object{value:NaN, done:true} var b = foo(5); b.next() // { value:6, done:false } b.next(12) // { value:8, done:false } b.next(13) // { value:42, done:true }
在以上代码中,第一次next()得到值6,下一次next()如果不传参数,x+1的值就被认为是undefined,后面继续执行结果都为NaN。但是如果传入了参数,比如例子中的12,也就是将x+1赋值为12,y=24,第二次next()的value为8。第三次传入13,y/3的值、z的值就为13,return结果就是5+24+13=42
如果一定想在第一次调用next()时就传参并生效,则需要在生成器外部包一个函数
function wrapper(genFunc){ return funtion(...arg){ var gen = genFunc(...arg); gen.next(); return gen; } } wrapper(funtion*(){ console.log(`first input ${yield}`) return 'Done'; }) wrapper().next('lalala')
因为在调用wrapper函数时,内部返回的函数就已经开启了迭代器,所以可以第一次next()时就传参且生效
for...of...
for...of...循环可以自动遍历Generator函数生成的迭代器,不需调用next()方法
function* foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } for (let v of foo()) { console.log(v);
// 1 2 3 4 5 }
当遍历器返回的done为true,for..of..就会中止
除了for...of
循环以外,扩展运算符(...
)、解构赋值和Array.from
方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。
Generator.prototype.throw
返回的遍历器对象,都有一个throw()方法,当执行出现错误时,抛出异常。这个异常可以被生成器内部的catch捕捉,也可以被全局catch捕捉。外部的全局throw也可以被内部的catch捕捉。如果生成器抛出的异常没有catch捕捉,那么遍历器会中止执行。
Generator.prototype.return
返回的遍历器对象,都有一个return()方法。当调用return()方法时,返回的value值就为return的值,done为true,也就是遍历器会中止执行。如果return()中不传参数,返回的value值为undefined
funtion* gen(){ yield 1; yield 2; yield 3; } var it = gen(); it.next(); // {value : 1, done : false} it.return(6); // {value : 6, done : true}
当生成器函数中存在try...finally..代码块,且正好执行到try代码块时,return()会直接开始执行finally中的代码,执行完后返回传入的值,遍历器中止
function* gen() { try{ yield 1; yield 2; }finally{ yield 3; yield 4; } } var it = gen(); it.next();// {value:1,done:false} it.return(7); {value:3,done:false
it.next();// {value:4,done:false}
it.next();// {value:7,done:true}
yield*
如果在一个生成器函数中调用另一个生成器函数,需要在前者函数内部对后者进行遍历
function* foo() { yield 'a'; yield 'b'; } function* bar() { yield 'x'; // 手动遍历 foo() for (let i of foo()) { console.log(i); } yield 'y'; } for (let v of bar()){ console.log(v); }
ES6中提供yield*解决
function* foo() { yield 'a'; yield 'b'; } function* bar() { yield 'x'; yield* foo(); yield 'y'; } for (let v of bar()){ console.log(v); }
// x a b y
如果使用的是yield而不是yield*返回的就是一个遍历器对象,而不是遍历的结果
当使用yield*遍历的生成器函数内部没有return语句时,就相当于使用for...of...遍历生成器函数。如果有return语句,就需要使用一个变量来接收return的值
只要被遍历的对象有iterator接口,就可以使用yield*进行遍历
Generator中的this
生成器返回的都是遍历器对象,可以得到生成器原型上的属性和方法。但是this并不指向这个遍历器对象。
异步场景下的使用
function myAjax(){ return fetch('https://img.alicdn.com/imgextra/i4/745475881/O1CN01VtOOnR1tJXl7Cnmcy_!!745475881.png'); } function genRunner(genFunc){ return new Promise(function(resolved, rejected){ var it = genFunc(); var innerRunner = function (data) { var val = it.next(data) if (val.done) { resolved(val.value) return } if (val.value) { val.value.then(data => { innerRunner(data) }) }else{ innerRunner(val.value) } } innerRunner(); } )}; genRunner(function*() { // generator函数中发起异步请求 var serverData = yield myAjax(); console.log('MyLogic after myAjax'); console.log('serverStatus:%s',serverData.status); }).then(function (message) { console.log(message) })
在以上代码中,执行genRunner函数,生成器作为参数传入。genRunner函数中首先调用生成器得到迭代器it,然后调用innerRunner方法。第一次调用时,执行至生成器yield关键字结束,即发起了一个ajax请求,返回了一个promise对象。在innerRunner中,val.value就是这个promise对象,通过.then执行next(),将响应结果传到生成器中,就拿到了最后的状态码。此时innerRunner中的val为{value:undefined, done:true},执行最后的resolved函数,value为undefined,因此输出undefined