异步编程系列教程:git
继上一篇见识过其配合promise带来的超爽的异步编程体验,我想应该大部分同窗都会想好好看一下,到底这个Generator是什么?接下来咱们会对Generator的特性进行剖析,让咱们对接下来学习co
源码打个扎实的基础。es6
咱们首先得知道,Generator一开始并非用来作异步编程的,是后来的大牛们挖掘了它的特性,让它在异步编程里大放异彩。其实Generator是生成遍历器的构造器,ES6定义了一个遍历器的接口Iterator。任何数据结构知足Iterator接口,均可以统一实现遍历操做。一步一步的调用next()
或者for..of
循环均可以遍历实现Iterator接口的数据结构。github
咱们简单说一下遍历对象的next()
是怎样的:编程
next()
会直接指向第一个数据的位置,而后返回数据的信息。结构是这样的:{value: AnyType, done: Boolean}
。value
属性是指该数据的值,done
则是标志是否已经true,结束了。next()
则指向下一个数据,返回相应的数据信息。{value: undefined, done: true}
。则表示遍历已经所有完成。这就是Iterator最基本的实现,固然这里是很片面的,若要展开说,基本又是一大篇文章能够写。这里就直接给出阮一峰老师关于Iterator的文章:10. Iterator和for...of循环promise
在咱们知道了Generator生成的遍历对象是什么以后,咱们看一下如何定义这样的Generator函数。对上一篇有印象的同窗,应该记得函数标识符后面有一个诡异的星号function* ()
。其实这个星号在括号前也是不要紧的,这里我是参考了co
源码的。咱们一旦定义了一个带星号的函数以后,用这个构造器生成的对象在harmony模式里就成了Generator对象(下面我会称其为遍历器)。咱们能够测试一下一段代码。数据结构
var toString = Object.prototype.toString; var Generator = function* (){ yield "hello"; yield "world"; }; var gen = Generator(); // 能够省去new来建立对象 console.log(toString.call(Generator)); // [object Function] console.log(toString.call(gen)); // [object Generator]
这样咱们经过调用特殊定义的Generator构造器,生成一个遍历器([object Generator])。那咱们要遍历的话必须得知道遍历的每一个成员,yield
就是用来定义遍历成员的。也就是说,遍历器进行遍历的时候会以yield
为间隔,一个yield
一个成员,不断往下走直到不存在下一个yield
。异步
在上面的例子中,就是第一次遍历到yield
获得"hello",第二次继续执行遍历操做到yield
获得"world",最后再执行就发现没有了,也就是done: true
结束遍历。异步编程
接下来咱们会详细说一下,遍历器是遍历的各类特性。函数
咱们须要执行遍历,首先就是要获得遍历器。前面也说过了,就是调用Generator构造器生成的。而后该遍历器会有一个方法next()
用来进行遍历操做,而且每一次的操做都会在yield
处中止,并等待下一次的next()
指令。咱们看一看刚才的代码:源码分析
var Generator = function* (){ yield "hello"; yield "world"; }; var gen = new Generator(); console.log(gen.next()); // { value: 'hello', done: false } console.log(gen.next()); // { value: 'world', done: false } console.log(gen.next()); // { value: undefined, done: true }
咱们能够看到最后当done: true
时,value
是undefined。其实咱们return出去一个值,就会成为该value
的值。其实换一个角度更加有意思,就是当你return出一个值,这个值一定是done: true
。咱们能够改一下上面的例子:
var Generator = function* (){ yield "hello"; return "world"; yield "!"; }; var gen = new Generator(); console.log(gen.next()); // { value: 'hello', done: false } console.log(gen.next()); // { value: 'world', done: true } console.log(gen.next()); // { value: undefined, done: true }
咱们能够看到,若是遍历器去找感叹号的yield
话,应该是value: '!'
。可是由于提早return结束了遍历器,因此最后获得了{ value: 'world', done: true }
。
咱们知道了每一次遍历器执行到yield
处后,会把值放在一个对象中的属性中返回出去。可是咱们在Generator构造器里怎么利用这个值呢?其实咱们能够为遍历器的next(res)
传入一个参数,这个参数将会成为这一次yield
的值。乍一看,好像不大清楚,看看代码就懂了。
var Generator = function* (){ var hello = yield "hello"; console.log(hello); // hi var world = yield "world"; console.log(world); // undefined }; var gen = new Generator(); var first = gen.next("nothing"); var second = gen.next("hi"); var third = gen.next();
咱们第一次next()
至关于启动器,这个时候传入任何参数都是被忽略的,由于这个参数没法做为上一个yield
的值(没有上一个)。到咱们第二次的next("hi")
,传入了一个"hi"字符串,这个参数就成为了yield
的值,直接赋值给hello变量并打印出来。咱们最后一个world变量是undefined,是由于next()
并无传入任何参数。能够这么说,每一次遍历器遍历获得的成员的值,和yield
的值是没有必然联系的。
因此咱们看代码的执行顺序也是颇有趣的一件事,遍历器会执行到语句yield
右侧即中止。等到下一次next()
启动,而后才会根据yield
获得的值,对语句左侧变量进行赋值。这样想的话,若是咱们下一次yield
语句,依赖第一次的值,咱们就须要在next()
里传入上一次的value
。咱们对上一次的代码作个小小的添加。
var first = gen.next("nothing"); var second = gen.next("hi"); var third = gen.next(second.value); //构造函数的world变量值也会是"hi"。
这个是Generator很是重要的特性,下去要好好实践一番,加深印象。接下来co
源码分析,这个特性配合promise能够放华丽的大招。
我起这个标题挺有意思的,哈哈哈。其实就和递归栈差很少,也就是说,当yield
的是另外一个遍历器,那么代码会进入到另外一个遍历器里,直到结束后,才交回代码控制权。看一看咯:
var Generator = function* (){ yield "hello"; yield *anotherGen; yield "world"; return "hello world"; }; var AnotherGenerator = function* (){ yield "强势插入!"; yield "不给hello world!"; } var gen = new Generator(); var anotherGen = new AnotherGenerator(); console.log(gen.next()); // { value: 'hello', done: false } console.log(gen.next()); // { value: '强势插入!', done: false } console.log(gen.next()); // { value: '不给hello world!', done: false } console.log(gen.next()); // { value: 'world', done: false } console.log(gen.next()); // { value: 'hello world', done: true }
当咱们须要遍历一个遍历器,那么*
也是须要的,能够参考一下上面。
咱们知道了遍历对象遍历时获得的什么,还有next(res)
传入参数有什么用,这对接下来的分析有着相当重要的做用。到这里,对Generator分析已是差很少了。若是想要更深刻了解的,能够去阮老师的博客看一看:11. Generator函数。
接下来一篇文章就是对co
源码的分析,先预习和复习一些东西吧。咱们回顾一下promise,咱们在将一个异步操做promise化后,当咱们调用这个异步操做,咱们会获得一个promise对象。因此咱们能够想象一下:
next()
获得该异步的promise对象then()
中的resolve
对数据进行处理next(res)
,进行下一次异步操做done: true
,结束遍历。这样咱们就能够一环扣一环的将Generator函数里的异步操做进行迭代,造成一种异步编程同步写法的优良体验。固然咱们这里不会详细说,如何去实现,由于我会在下一篇好好讲讲。