序
在Javascript中,大家讨论的最多的就是异步编程的操作,如何避免回调的多次嵌套。异步操作的回调一旦嵌套很多,不仅代码会变的臃肿,还很容易出错。各种各样的异步编程解决方案也被不断提出,例如大家所熟知的Promise,co等等。今天所讲的Generator和yield就是和异步编程有关,可以帮助我们把异步编程同步化。
Generator简介
Generator在形式上和函数差不多,只是在function和函数名之间多了一个*。Generator内部必须使用yield关键字。例如:
function * gen(){
var result1 = yield 'hello';
var result2 = yield 'world';
return result1 + result2;
}
当调用Generator函数时,并不会执行函数内部的代码,而是返回一个遍历器,该遍历器包含一个next方法。每次执行next方法,Generator函数体会开始执行,直到遇到yield语句,执行该语句并在此暂停。用法如下:
var g = gen();
g.next(1);
//{value : 'hello', done : false}
g.next(2);
//{value : 'world', done : false}
g.next();
//{value : 3, done: true}
g.next();
//{value : undefined, done: true}
调用next方法会返回一个对象,这个对象包含两个属性,value和done,value即是当前yield语句的值。done表示Generator函数体是否被执行完。next方法同时接受一个参数,这个参数会作为yield语句的返回值,可以被后面的程序所使用。当程序执行完或者遇到return语句,value即为函数体的返回值,done就变成了true。至此,如果再执行next方法,value就为undefined, done依然是true。
Generator在遍历中的应用
在js中,我们要遍历一个数组,我们可以用for...of这样的语句来进行遍历,这其实也是因为数组中包含了一个Generator遍历器。如果我们的自己定义的对象也包含一个遍历器,我们也就可以通过for...of等遍历语句来遍历自定义的对象。这个遍历器被存在Symbol.iterator属性中。
var myArray = {
0: '你',
1: '的',
2: '名字',
length: 3
};
myArray[Symbol.iterator] = function * (){
for(var i = 0; i < this.length; i++) {
yield this[i];
}
};
for(var item of myArray) {
console.log(item);
}
//你
//的
//名字
Generator在异步编程中的应用
Javascript的核心就是异步编程,每个异步操作都会提供一个callback回调函数来返回执行的结果。假设我们有几个操作,后一个操作依赖前一个操作的结果,如果采用回调的方式:
step1(function(value1) {
step2(value1, function(value2) {
step3(value2, function(value3)) {
//some code
}
});
})
这样的代码一单回调的嵌套变多,会让程序变的非常难理解,同时也很容易出错。我们要做的就是将回调变的扁平化。Promise对象就是这样的功能,将上述的操作Promise化:
step1().then(function(value1){
return step2(value1);
}).then(function(value2){
return step3(value2);
}).then(function(){
//some code
})
我们可以看到嵌套变少了,但是这并不是最理想的解决方案,如果我们能将异步操作变成同步操作那样,即没了嵌套,程序也会变的好理解。Generator函数就给我们提供的这样的机会。
function *workflow(){
var value1 = yield step1();
var value2 = yield step2();
var value3 = yield step3();
//some code
}
这样就是我们希望结果,异步编程编程了同步编程的形式。我们接下来要做的是让这个Generator执行起来,所以我们需要一个执行器。co就是一个执行器,让Generator自动执行。
co(function *workflow(){
var value1 = yield step1();
var value2 = yield step2();
var value3 = yield step3();
//some code
});
co有个限制,yield语句后面跟的只能是Promise对象或者Thunk函数,关于co更详细的介绍,可以参考阮老师的文章co 函数库的含义和用法。然而这样的方法依然需要依赖外在的库函数,于是ES6中提出了async和await关键字。async和await其实就是Generator的语法糖。只是它自带执行器。将上面的代码改写成async形式:
async function workflow(){
var value1 = await step1();
var value2 = await step2();
var value3 = await step3();
//some code
}
var result = workflow();
async没有了co的限制。await关键字后面可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。