異步編程系列教程: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函數裏的異步操做進行迭代,造成一種異步編程同步寫法的優良體驗。固然咱們這裏不會詳細說,如何去實現,由於我會在下一篇好好講講。