異步編程之Generator(2)——剖析特性

異步編程系列教程:git

  1. (翻譯)異步編程之Promise(1)——初見魅力
  2. 異步編程之Promise(2):探究原理
  3. 異步編程之Promise(3):拓展進階
  4. 異步編程之Generator(1)——領略魅力
  5. 異步編程之Generator(2)——剖析特性
  6. 異步編程之co——源碼分析

Generator基礎


繼上一篇見識過其配合promise帶來的超爽的異步編程體驗,我想應該大部分同窗都會想好好看一下,到底這個Generator是什麼?接下來咱們會對Generator的特性進行剖析,讓咱們對接下來學習co源碼打個紮實的基礎。es6

起源

咱們首先得知道,Generator一開始並非用來作異步編程的,是後來的大牛們挖掘了它的特性,讓它在異步編程裏大放異彩。其實Generator是生成遍歷器的構造器,ES6定義了一個遍歷器的接口Iterator。任何數據結構知足Iterator接口,均可以統一實現遍歷操做。一步一步的調用next()或者for..of循環均可以遍歷實現Iterator接口的數據結構。github

咱們簡單說一下遍歷對象的next()是怎樣的:編程

  1. 第一次調用next()會直接指向第一個數據的位置,而後返回數據的信息。結構是這樣的:{value: AnyType, done: Boolean}value屬性是指該數據的值,done則是標誌是否已經true,結束了。
  2. 再一次調用next()則指向下一個數據,返回相應的數據信息。
  3. 重複第二步,一直到數據結束,返回{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特性


遍歷

咱們須要執行遍歷,首先就是要獲得遍歷器。前面也說過了,就是調用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傳值

咱們知道了每一次遍歷器執行到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對象。因此咱們能夠想象一下:

  1. 咱們調用遍歷器的next()獲得該異步的promise對象
  2. 在promise對象的then()中的resolve對數據進行處理
  3. 把數據做爲參數傳入next(res),進行下一次異步操做
  4. 直到迭代器的done: true,結束遍歷。

這樣咱們就能夠一環扣一環的將Generator函數裏的異步操做進行迭代,造成一種異步編程同步寫法的優良體驗。固然咱們這裏不會詳細說,如何去實現,由於我會在下一篇好好講講。

相關文章
相關標籤/搜索