異步編程系列教程:
-
(翻譯)異步編程之Promise(1)——初見魅力
-
異步編程之Promise(2):探究原理
-
異步編程之Promise(3):拓展進階
-
異步編程之Generator(1)——領略魅力
-
異步編程之Generator(2)——剖析特性
- 異步編程之co——源碼分析
Generator基礎
繼上一篇見識過其配合promise帶來的超爽的異步編程體驗,我想應該大部分同學都會想好好看一下,到底這個Generator是什么?接下來我們會對Generator的特性進行剖析,讓我們對接下來學習
co
源碼打個扎實的基礎。
起源
我們首先得知道,Generator一開始并不是用來做異步編程的,是后來的大牛們挖掘了它的特性,讓它在異步編程里大放異彩。其實Generator是生成遍歷器的構造器,ES6定義了一個遍歷器的接口Iterator。任何數據結構滿足Iterator接口,都可以統一實現遍歷操作。一步一步的調用
next()
或者
for..of
循環都可以遍歷實現Iterator接口的數據結構。
我們簡單說一下遍歷對象的
next()
是怎樣的:
-
第一次調用
next()
會直接指向第一個數據的位置,然后返回數據的信息。結構是這樣的:{value: AnyType, done: Boolean}
。value
屬性是指該數據的值,done
則是標志是否已經true,結束了。
-
再一次調用
next()
則指向下一個數據,返回相應的數據信息。
-
重復第二步,一直到數據結束,返回
{value: undefined, done: true}
。則表示遍歷已經全部完成。
這就是Iterator最基本的實現,當然這里是很片面的,若要展開說,基本又是一大篇文章可以寫。這里就直接給出阮一峰老師關于Iterator的文章: 10. Iterator和for...of循環
定義
在我們知道了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對象。所以我們可以想象一下:
-
我們調用遍歷器的
next()
得到該異步的promise對象
-
在promise對象的
then()
中的resolve
對數據進行處理
-
把數據作為參數傳入
next(res)
,進行下一次異步操作
-
直到迭代器的
done: true
,結束遍歷。
這樣我們就可以一環扣一環的將Generator函數里的異步操作進行迭代,形成一種異步編程同步寫法的優良體驗。當然我們這里不會詳細說,如何去實現,因為我會在下一篇好好講講。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
