一、前言
在上一篇文章中我记录了Promise的用法:es 异步流程之Promise篇
而这次记录一个强大的东西---generator(生成器)。可能有人会问:“promise不是已经大大改善回调地狱了吗?干嘛又折腾一个generator?promise不会白学了?”
虽然 Promise 代码不会形成 回调地狱的 “>” 形,但是也变成了直直的一条下来:
var p1 = new Promise((resolve,reject)=>{
...
})
p1
.then(step1)
.then(step2)
.then(step3)
.then(step4)
//...
这样的代码并不直观,实际上我们往往更加喜欢同步化的编程,举个栗子:
function asyncFn(){
var result1 = step1();
var result2 = step2(result1);
var result3 = step3(result2);
//...
}
要是能这样异步编程该多好呢?
嘿嘿嘿,generator的作用就出现了(ps:不过有点买家秀的感觉噢,会多一些额外的东西)
二、generator基本语法
1.generator函数格式与generator()
generator函数格式为:
function * generator(){
//...
}
注意这个 * 符号,它是必备的。
另外generator函数的执行流程与普通函数不一样
普通函数执行后返回return的值,而generator执行后返回一个遍历器。
//普通函数
function fn(){
return "r1"
}
fn() // 结果为:r1
//generator
function * generator(){
//...
}
var g = generator() // 结果为:遍历器
g.next() //执行第一次
//...
2.next
next是运行遍历器的方法,调用next后便真正地开始执行generator函数里的代码。每次调用next后它都会返回一个对象形如:
{
value:"", //yield 或 return 的值
done:false //true为函数结束,false为函数未结束
}
举个栗子:
function* generator(){
var a = 100;
var b = 200;
return a+b;
}
var g = generator();
console.log(g.next());
//输出:{ value: 300, done: true }
目前你只需要记住,next 会执行generator函数代码并返回一个固定形式对象即可。
3.yield
yield 是generator的特别之处,它意为:“ 产出 ”。
调用next后,开始执行generator函数代码,当运行到yield表达式时,把它后面的值作为next返回对象的value产出,并暂停执行当前函数。
这样说起来还是很难理解,很诡异。还是看个栗子图吧:
这是普通函数执行流程:
这是generator函数执行流程 :
可以看到,调用第一个 next 后,到 yield表达式 便返回 yield 后面的值,上图第一个yield出的是100,然后暂停generator函数,直到调用下一个 next 。
调用下一个 next 后,接着从上一个yield语句继续运行直到遇见下一个yield 。你可以想象为 yield 把 一个语句分成两部分:
var a = yield 100; // 原语句
//先执行一半语句
yield 100
//暂停等待next调用...
//next调用后,再执行另一半
var a =
//继续执行,直到遇见yield,然后如上述流程执行
以此类推,每次 next 后generator函数都会在下一个yield处停止并返回值。如果遇见 return 或者 函数结束了,done就变成true
举个代码栗子:
function* generator(){
var a = yield 100;
var b = yield 200;
return a+b;
}
var g = generator();
console.log(g.next()); //输出:{ value:100,done:false }
console.log(g.next()); //输出:{ value:200,done:false }
console.log(g.next()); //输出:{ value:NaN,done:true }
可能有人会问为什么最后 “ return a+b ” 的返回的值是 NaN (Not a Number)?不应该是 300 吗?
很多人会简单地认为,yield 出的值会接着赋值给左边的变量,例如: var a = 100。
其实不是这样的,yield 只负责产出它后边的值到函数外部 ,相对的,我们还需要输入一个值到函数内部。负责输入的正是带参数的next。
4.next带参数
看个栗子图:
yield 赋给左边变量的值 即为 next 输入的参数。
看个代码栗子:
function* generator(){
var a = yield 100;
var b = yield 200;
return a+b;
}
var g = generator();
console.log(g.next()); //输出:{ value:100,done:false }
console.log(g.next(200)); //next输入200, a=200, 输出:{ value:200,done:false }
console.log(g.next(400)); //next输入400, a=400, 输出:{ value:600,done:true }
如上述代码,a为next输入的200,b为next输入的400。
三、generator异步应用
说了那么多,终于说到异步应用辽。其实在generator中,promise还是十分重要的,需要用上它来异步编程。
先定义两个返回Promise对象的函数,作为异步操作函数:
function step1(){
return new Promise((resolve,reject)=>{
setTimeout(()=>resolve(100),1000);
})
}
function step2(value){
return new Promise((resolve,reject)=>{
setTimeout(()=>resolve(value+200),1000);
})
}
举个简单例子:
function* generator(){
var a = yield step1();
return a;
}
var g = generator();
console.log(g.next()); //输出:{ value: Promise { <pending> }, done: false }
yield step1() 的执行顺序是,先调用step1() 返回Promise对象,然后 yield 把promise对象产出到外部。所以可看到返回的value为 Promise { <pending> }
由于我们想要把 promise resolve后的结果输入给 a ,所以得把结果用next传入。
function* generator(){
var a = yield step1();
return a;
}
var g = generator();
var promise = g.next().value; //产出了 promise
promise.then((result)=>{ //用 then 获取resolve结果
var ret = g.next(result); //用 next 传入给a
console.log(ret); // { value:100,done:true}
})
看个稍微复杂点的栗子:
function* generator(){
var a = yield step1();
var b = yield step2(a);
return a+b;
}
var g = generator();
var promise = g.next().value; //产出了 promise
promise.then((result)=>{ //用 then 获取resolve结果
var promise2 = g.next(result).value; //用 next 传入给a,然后返回了step2的promise对象
promise2.then((result2)=>{
var ret = g.next(result); //用 next 传入给b,然后返回 a+b 结果
console.log(ret); // 输出:{ value:400, done: true };
})
})
看起来很复杂,实际上很简单。首先,得到step1的promise对象,在then中把结果(100)用 next 传入a (a = 100)。接着得到step2的promise2,同样道理,在promise2 的 then中把结果(a+200)用 next 传入b (b = 300)。最后得到 a+b (400)。
这时候有人要按耐不住了:“哈?看了这么久,结果generator的代码比promise的还要多,还要麻烦?把老子意大利炮拿来!”
四、自动化
客观请留步,其实有一些步骤可以自动完成。让我们来封装一下。
先看一下之前的代码:
var promise = g.next().value;
promise.then((result)=>{
var promise2 = g.next(result).value;
promise2.then((result2)=>{
var ret = g.next(result);
})
})
有没有发现 promise.then 里 的函数代码非常地相似。
看个夸张版的:
var promise = g.next().value;
promise.then((result)=>{
var promise2 = g.next(result).value;
promise2.then((result)=>{
var promise3 = g.next(result).value;
promise3.then((result)=>{
var promise4 = g.next(result).value;
promise4.then((result)=>{
var promise5 = g.next(result).value;
...
})
})
})
})
可以看到,传给 then 的函数格式是一毛一样的!都是得到 result,输入给next,然后得到新promise,最后把同格式函数传给新promise的 then 。我们把它抽离出来:
//该函数命名为 next
function next(result){
var promise = g.next(result).value;
promise.then((result)=>{
next(result); //关键在于这里的传入 next ,像递归一样。
})
}
这样子看来,貌似能自动执行promise的调用,不过还有问题没解决,什么时候停止next函数呢?
那么改进一下:
//该函数命名为 next
function next(result){
const { value:p , done } = g.next(result);
if(done) return;
p.then(next);
}
最后,再把generator的遍历器和上述next函数封装起来:
//命名为 auto
function auto(gen){
var g = gen();
function next(result){
var {value:p,done} = g.next(result);
if(done) return;
p.then(next);
}
next();
}
举个栗子测试下:
function* generator(){
var a = yield step1();
var b = yield step2(a);
console.log(a+b); //输出:400
}
auto(generator);
和理想中的同步方式对比下,只是多个 * 、yield 、和一个自动化函数auto。
实际上,这里我只实现了非常简单的自动化功能,它不能处理除promise外的方式,不能把generator函数返回值返回(最后我用console.log(a+b)而不是return a+b 就是因为它无法返回结果。)。
其实已经有一些现成的强大自动化工具了,如著名的 co 由 Tj大神所写。可以直接使用它来管理自动化。