異步編程系列教程:
-
(翻譯)異步編程之Promise(1)——初見魅力
-
異步編程之Promise(2):探究原理
-
異步編程之Promise(3):拓展進(jìn)階
-
異步編程之Generator(1)——領(lǐng)略魅力
-
異步編程之Generator(2)——剖析特性
- 異步編程之co——源碼分析
Generator基礎(chǔ)
繼上一篇見識(shí)過其配合promise帶來的超爽的異步編程體驗(yàn),我想應(yīng)該大部分同學(xué)都會(huì)想好好看一下,到底這個(gè)Generator是什么?接下來我們會(huì)對(duì)Generator的特性進(jìn)行剖析,讓我們對(duì)接下來學(xué)習(xí)
co
源碼打個(gè)扎實(shí)的基礎(chǔ)。
起源
我們首先得知道,Generator一開始并不是用來做異步編程的,是后來的大牛們挖掘了它的特性,讓它在異步編程里大放異彩。其實(shí)Generator是生成遍歷器的構(gòu)造器,ES6定義了一個(gè)遍歷器的接口Iterator。任何數(shù)據(jù)結(jié)構(gòu)滿足Iterator接口,都可以統(tǒng)一實(shí)現(xiàn)遍歷操作。一步一步的調(diào)用
next()
或者
for..of
循環(huán)都可以遍歷實(shí)現(xiàn)Iterator接口的數(shù)據(jù)結(jié)構(gòu)。
我們簡單說一下遍歷對(duì)象的
next()
是怎樣的:
-
第一次調(diào)用
next()
會(huì)直接指向第一個(gè)數(shù)據(jù)的位置,然后返回?cái)?shù)據(jù)的信息。結(jié)構(gòu)是這樣的:{value: AnyType, done: Boolean}
。value
屬性是指該數(shù)據(jù)的值,done
則是標(biāo)志是否已經(jīng)true,結(jié)束了。
-
再一次調(diào)用
next()
則指向下一個(gè)數(shù)據(jù),返回相應(yīng)的數(shù)據(jù)信息。
-
重復(fù)第二步,一直到數(shù)據(jù)結(jié)束,返回
{value: undefined, done: true}
。則表示遍歷已經(jīng)全部完成。
這就是Iterator最基本的實(shí)現(xiàn),當(dāng)然這里是很片面的,若要展開說,基本又是一大篇文章可以寫。這里就直接給出阮一峰老師關(guān)于Iterator的文章: 10. Iterator和for...of循環(huán)
定義
在我們知道了Generator生成的遍歷對(duì)象是什么之后,我們看一下如何定義這樣的Generator函數(shù)。對(duì)上一篇有印象的同學(xué),應(yīng)該記得函數(shù)標(biāo)識(shí)符后面有一個(gè)詭異的星號(hào)
function* ()
。其實(shí)這個(gè)星號(hào)在括號(hào)前也是沒關(guān)系的,這里我是參考了
co
源碼的。我們一旦定義了一個(gè)帶星號(hào)的函數(shù)之后,用這個(gè)構(gòu)造器生成的對(duì)象在harmony模式里就成了Generator對(duì)象(下面我會(huì)稱其為遍歷器)。我們可以測試一下一段代碼。
var toString = Object.prototype.toString;
var Generator = function* (){
yield "hello";
yield "world";
};
var gen = Generator(); // 可以省去new來創(chuàng)建對(duì)象
console.log(toString.call(Generator)); // [object Function]
console.log(toString.call(gen)); // [object Generator]
這樣我們通過調(diào)用特殊定義的Generator構(gòu)造器,生成一個(gè)遍歷器([object Generator])。那我們要遍歷的話必須得知道遍歷的每個(gè)成員,
yield
就是用來定義遍歷成員的。也就是說,遍歷器進(jìn)行遍歷的時(shí)候會(huì)以
yield
為間隔,一個(gè)
yield
一個(gè)成員,不斷往下走直到不存在下一個(gè)
yield
。
在上面的例子中,就是第一次遍歷到
yield
得到"hello",第二次繼續(xù)執(zhí)行遍歷操作到
yield
得到"world",最后再執(zhí)行就發(fā)現(xiàn)沒有了,也就是
done: true
結(jié)束遍歷。
接下來我們會(huì)詳細(xì)說一下,遍歷器是遍歷的各種特性。
Generator特性
遍歷
我們需要執(zhí)行遍歷,首先就是要得到遍歷器。前面也說過了,就是調(diào)用Generator構(gòu)造器生成的。然后該遍歷器會(huì)有一個(gè)方法
next()
用來進(jìn)行遍歷操作,并且每一次的操作都會(huì)在
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 }
我們可以看到最后當(dāng)
done: true
時(shí),
value
是undefined。其實(shí)我們r(jià)eturn出去一個(gè)值,就會(huì)成為該
value
的值。其實(shí)換一個(gè)角度更加有意思,就是當(dāng)你return出一個(gè)值,這個(gè)值必定是
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 }
我們可以看到,如果遍歷器去找感嘆號(hào)的
yield
話,應(yīng)該是
value: '!'
。但是因?yàn)樘崆皉eturn結(jié)束了遍歷器,所以最后得到了
{ value: 'world', done: true }
。
yield傳值
我們知道了每一次遍歷器執(zhí)行到
yield
處后,會(huì)把值放在一個(gè)對(duì)象中的屬性中返回出去。但是我們在Generator構(gòu)造器里怎么利用這個(gè)值呢?其實(shí)我們可以為遍歷器的
next(res)
傳入一個(gè)參數(shù),這個(gè)參數(shù)將會(huì)成為這一次
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()
相當(dāng)于啟動(dòng)器,這個(gè)時(shí)候傳入任何參數(shù)都是被忽略的,因?yàn)檫@個(gè)參數(shù)無法作為上一個(gè)
yield
的值(沒有上一個(gè))。到我們第二次的
next("hi")
,傳入了一個(gè)"hi"字符串,這個(gè)參數(shù)就成為了
yield
的值,直接賦值給hello變量并打印出來。我們最后一個(gè)world變量是undefined,是因?yàn)?
next()
并沒有傳入任何參數(shù)。可以這么說,每一次遍歷器遍歷得到的成員的值,和
yield
的值是沒有必然聯(lián)系的。
所以我們看代碼的執(zhí)行順序也是很有趣的一件事,遍歷器會(huì)執(zhí)行到語句
yield
右側(cè)即停止。等到下一次
next()
啟動(dòng),然后才會(huì)根據(jù)
yield
得到的值,對(duì)語句左側(cè)變量進(jìn)行賦值。這樣想的話,如果我們下一次
yield
語句,依賴第一次的值,我們就需要在
next()
里傳入上一次的
value
。我們對(duì)上一次的代碼做個(gè)小小的添加。
var first = gen.next("nothing");
var second = gen.next("hi");
var third = gen.next(second.value); //構(gòu)造函數(shù)的world變量值也會(huì)是"hi"。
這個(gè)是Generator非常重要的特性,下去要好好實(shí)踐一番,加深印象。接下來
co
源碼分析,這個(gè)特性配合promise可以放華麗的大招。
遍歷遍歷器里的遍歷器
我起這個(gè)標(biāo)題挺有意思的,哈哈哈。其實(shí)就和遞歸棧差不多,也就是說,當(dāng)
yield
的是另一個(gè)遍歷器,那么代碼會(huì)進(jìn)入到另一個(gè)遍歷器里,直到結(jié)束后,才交回代碼控制權(quán)。看一看咯:
var Generator = function* (){
yield "hello";
yield *anotherGen;
yield "world";
return "hello world";
};
var AnotherGenerator = function* (){
yield "強(qiáng)勢插入!";
yield "不給hello world!";
}
var gen = new Generator();
var anotherGen = new AnotherGenerator();
console.log(gen.next()); // { value: 'hello', done: false }
console.log(gen.next()); // { value: '強(qiáng)勢插入!', 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 }
當(dāng)我們需要遍歷一個(gè)遍歷器,那么
*
也是需要的,可以參考一下上面。
總結(jié)
我們知道了遍歷對(duì)象遍歷時(shí)得到的什么,還有
next(res)
傳入?yún)?shù)有什么用,這對(duì)接下來的分析有著至關(guān)重要的作用。到這里,對(duì)Generator分析已經(jīng)是差不多了。如果想要更深入了解的,可以去阮老師的博客看一看:
11. Generator函數(shù)
。
接下來一篇文章就是對(duì)
co
源碼的分析,先預(yù)習(xí)和復(fù)習(xí)一些東西吧。我們回顧一下promise,我們在將一個(gè)異步操作promise化后,當(dāng)我們調(diào)用這個(gè)異步操作,我們會(huì)得到一個(gè)promise對(duì)象。所以我們可以想象一下:
-
我們調(diào)用遍歷器的
next()
得到該異步的promise對(duì)象
-
在promise對(duì)象的
then()
中的resolve
對(duì)數(shù)據(jù)進(jìn)行處理
-
把數(shù)據(jù)作為參數(shù)傳入
next(res)
,進(jìn)行下一次異步操作
-
直到迭代器的
done: true
,結(jié)束遍歷。
這樣我們就可以一環(huán)扣一環(huán)的將Generator函數(shù)里的異步操作進(jìn)行迭代,形成一種異步編程同步寫法的優(yōu)良體驗(yàn)。當(dāng)然我們這里不會(huì)詳細(xì)說,如何去實(shí)現(xiàn),因?yàn)槲視?huì)在下一篇好好講講。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

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