es 异步流程(二)之generator篇

3 篇文章 0 订阅

一、前言

在上一篇文章中我记录了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大神所写。可以直接使用它来管理自动化。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值