摘要:需要說明的是,每次執(zhí)行完函數(shù)之后,都會返回一個對象這個返回值有兩個屬性和,對象通過這個返回值來告訴外界函數(shù)的執(zhí)行情況。函數(shù)的返回值變成這樣可以發(fā)現(xiàn)的值變?yōu)榱?,因為函?shù)已經(jīng)執(zhí)行完了。在規(guī)范中,新增了兩個協(xié)議可迭代協(xié)議和迭代器協(xié)議。
Koa是最近比較火的一款基于Node的web開發(fā)框架。說他是一個框架,其實他更像是一個函數(shù)庫,通過某種思想(或者說某種約定),將眾多的中間件聯(lián)系在一起,從而提供你所需要的web服務(wù)。
Koa做了兩件很重要的事:
封裝node的request和response對象到Context上,還提供了一些開發(fā)web應(yīng)用以及api常用的方法
提供了一套流程控制方式,將眾多中間件級聯(lián)在一起
而我現(xiàn)在想討論的就是Koa的這套流程控制的思想。
先看一段從官方文檔上搬下來的代碼:
var koa = require("koa"); var app = koa(); // x-response-time app.use(function *(next){ var start = new Date; yield next; var ms = new Date - start; this.set("X-Response-Time", ms + "ms"); }); // logger app.use(function *(next){ var start = new Date; yield next; var ms = new Date - start; console.log("%s %s - %s", this.method, this.url, ms); }); // response app.use(function *(){ this.body = "Hello World"; }); app.listen(3000);
app是Koa的一個實例,通過調(diào)用app.use,向Koa內(nèi)部維護的一個middlewares數(shù)組中,添加中間件。而我們所說的中間件,其實就是那個作為app.use參數(shù)的,使用奇怪方式聲明的function。
在Koa中,我們約定所有的中間件都是以這種方式聲明的,如果你了解ES6,那你一定見過這種聲明方式。沒錯,這就是ES6中的generator function。Koa中,真正的中間件其實就是一個generator對象。
什么是Generator?Generator是ES6新引進的一個概念,使用Generator可以將函數(shù)的控制權(quán)交給函數(shù)外部。也就是說,你可以控制函數(shù)的執(zhí)行進程。
舉個例子:
function *sayHello(){ console.log("before say"); yield console.log("hello!"); console.log("end say"); } var a = sayHello(); a.next(); // 輸出before say 輸出hello! a.next(); // 輸出end say
首先我們定義了一個叫做sayHello的generator function,它跟普通的function不同,執(zhí)行sayHello(),并不會執(zhí)行函數(shù)體內(nèi)部的程序,但是會返回一個generator對象。因此a的值實際上長這樣:
sayHello {[[GeneratorStatus]]: "suspended"}
對generator function來說,執(zhí)行函數(shù)只是生成了一個generator對象,不會執(zhí)行函數(shù)的內(nèi)在邏輯,而使用者卻可以通過這個generator對象來達到控制函數(shù)執(zhí)行的目的。就比如說這個sayHello函數(shù),我可以在需要的時候,執(zhí)行a.next()方法,來執(zhí)行函數(shù)的內(nèi)部邏輯。第一次執(zhí)行a.next(),函數(shù)開始執(zhí)行,直到它遇到yield指令,它會執(zhí)行yield之后的表達式,并返回一個值,然后中斷函數(shù)的運行。因此,我們看到,第一次執(zhí)行a.next()后,函數(shù)輸出了"before say"和"hello!"。需要說明的是,每次執(zhí)行完next函數(shù)之后,都會返回一個對象:
Object {value: undefined, done: false}
這個返回值有兩個屬性:value和done,generator對象通過這個返回值來告訴外界函數(shù)的執(zhí)行情況。value的值是yield之后的表達式的值,done則是函數(shù)執(zhí)行的狀態(tài),如果函數(shù)未執(zhí)行完,則其值為false,否則是true。在sayHello中,yield之后是console語句,因此返回的對象中value為undefined。
這個時候,我們再次調(diào)用a.next(),程序輸出"end say"。next函數(shù)的返回值變成這樣:
Object {value: undefined, done: true}
可以發(fā)現(xiàn)done的值變?yōu)榱藅rue,因為函數(shù)已經(jīng)執(zhí)行完了。
Generator可以被用來作迭代器。
首先了解一下迭代器。在ES6規(guī)范中,新增了兩個協(xié)議:可迭代協(xié)議和迭代器協(xié)議。在迭代器協(xié)議中指明,一個實現(xiàn)了next方法并且該方法的返回值有done和value兩個屬性的對象,可以被當做迭代器。這些要求正好符合我們的Generator對象。舉一個被當做迭代器使用的例子:
function *range(start, end){ for (let i = start; i < end; i++) { yield i; } } var a = range(0, 10); // 輸出0...9 for (let i of a) { console.log(i); }
其實道理是一樣的,Generator把程序的控制權(quán)交給了外部,哪里調(diào)用next,程序就在哪里執(zhí)行??上攵?strong>for...of的實現(xiàn)原理也一定是在內(nèi)部循環(huán)執(zhí)行了next方法,直到返回值的done屬性變成true才停止。
為什么中間件必須是個Generator function?了解了Generator,回頭再去看那段官方文檔上搬來的代碼。
var koa = require("koa"); var app = koa(); // x-response-time app.use(function *(next){ var start = new Date; yield next; var ms = new Date - start; this.set("X-Response-Time", ms + "ms"); }); // logger app.use(function *(next){ var start = new Date; yield next; var ms = new Date - start; console.log("%s %s - %s", this.method, this.url, ms); }); // response app.use(function *(){ this.body = "Hello World"; }); app.listen(3000);
我們來分析代碼。app.use將一個個中間件放入middlewares數(shù)組中,而app.listen啟動了一個3000端口來監(jiān)聽http服務(wù)。實際上app.listen這個方法,底層是這樣實現(xiàn)的:
var http = require("http"); var koa = require("koa"); var app = koa(); http.createServer(app.callback()).listen(3000);
這樣你就明白了,當請求來臨時,會觸發(fā)在createServer時注冊的回調(diào)函數(shù)(app.callback()的返回值),這個回調(diào)函數(shù)的執(zhí)行其實就引發(fā)了一連串的中間件的執(zhí)行。
先說結(jié)果,在探索原理。
middlewares數(shù)組中的這些中間件順序執(zhí)行,先開始進入第一個中間件 —— x-response-time,遇到y(tǒng)ield中斷執(zhí)行,轉(zhuǎn)而進入第二個中間件 —— logger,同樣遇到y(tǒng)ield中斷執(zhí)行,進入第三個中間件 —— response,這次沒有遇到y(tǒng)ield,第三個中間件執(zhí)行完畢,頁面輸出"Hello World",done的值變?yōu)閠rue。這個時候,再返回去執(zhí)行第二個中間件剛剛中斷的地方,直到第二個中間件的done也變?yōu)閠rue,返回第一個中間件剛剛中斷的位置。
是不是很神奇?這些中間件就像洋蔥一樣,一層一層的深入進去,又一層一層的走出來。
那么Koa是如何實現(xiàn)這般神奇的流程控制的呢?
Koa內(nèi)部依賴了一個叫co的流程控制庫。
首先,Koa實現(xiàn)了一個叫Koa-compose的中間件,這個中間件用來將middlewares中的所有中間件串聯(lián)起來。其實現(xiàn)代碼如下:
/** * Compose `middleware` returning * a fully valid middleware comprised * of all those which are passed. * * @param {Array} middleware * @return {Function} * @api public */ function compose(middleware){ return function *(next){ if (!next) next = noop(); var i = middleware.length; while (i--) { next = middleware[i].call(this, next); } return yield *next; } } /** * Noop. * * @api private */ function *noop(){}
compose函數(shù)會返回一個能將眾多中間件串聯(lián)起來的Generator函數(shù)。這個函數(shù)從最后一個中間件開始執(zhí)行,將生成的Generator對象扔給它的上一個中間件,依次類推,直到第一個中間件。這個結(jié)構(gòu)真的很像一顆洋蔥,從最后一個中間件開始,一層一層往上面包。
這樣生成一個Generator對象之后,Koa把它交給了co這個流程控制庫。co其實是個很抽象的東西。為了理解它的原理,我們可以先思考一下,如果把這個Generator對象交給我們,我們怎么類似于實現(xiàn)剛剛那個圖所展示的效果?
從洋蔥的最外層皮開始往里剝。執(zhí)行第一次.next()函數(shù),第一層中間件yield之前的程序執(zhí)行完畢,通過yield next,我們拿到了第二層中間件的Generator對象。這個時候怎么辦呢?按照剛剛那幅圖,第一層中間件,必須要等到第二層中間件的done狀態(tài)變?yōu)閠rue之后,才可以繼續(xù)執(zhí)行之后的程序,即只有在第二層中間件的done狀態(tài)變?yōu)閠rue之后,才能再次執(zhí)行第一層中間件Generator對象的.next()函數(shù)。同樣的,之后所有的中間件都要重復這樣的過程,第一層等待第二層,第二層等待第三層......那么當狀態(tài)改變的時候,是不是應(yīng)該有個人來通知我們?對,這個時候Promise就該出場了。
co將每個中間件.next()的運行結(jié)果的value屬性都封裝成一個Promise,在其done狀態(tài)變?yōu)閠rue時,resolve()這個Promise,對于洋蔥里面的部分,每一層resolve之后,都會觸發(fā)上一層中間件的.next()函數(shù),并檢查其狀態(tài)。直到洋蔥的最外面一層也resolve了,控制權(quán)就交還給Koa,而Koa會在這個時候,發(fā)起response。
co的大體思想就是這樣,如果想繼續(xù)深入,可以去看co的源碼,自己實現(xiàn)一下應(yīng)該也不會太難。
理解了洋蔥模型,就不難明白,yield和Promise在其中所起的作用了。
關(guān)于Koa關(guān)于Koa,還有太多值得拿出來討論的話題,我現(xiàn)在只是對Koa1.x中對Generator的使用做了一次整理,別的話題就慢慢再討論吧。
最后,如果你有什么建議,歡迎不吝賜教~
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/88264.html
摘要:到此為止,我們就基本講清楚了中的中間件洋蔥模型是如何自動執(zhí)行的。 koa被認為是第二代web后端開發(fā)框架,相比于前代express而言,其最大的特色無疑就是解決了回調(diào)金字塔的問題,讓異步的寫法更加的簡潔。在使用koa的過程中,其實一直比較好奇koa內(nèi)部的實現(xiàn)機理。最近終于有空,比較深入的研究了一下koa一些原理,在這里會寫一系列文章來記錄一下我的學習心得和理解。 在我看來,koa最核心...
摘要:返回后,代表操作已完成,記錄結(jié)束時間并輸出。從零組裝因為對的學習和使用,知道了自己對于后臺框架的真實需求。所以這回決定不用之內(nèi)的工具,而是自己從零開始,組裝一個適合自己的框架。就是去和上,尋找一個一個的包并組裝在一起了而已。 起因 作為一個前端,Node.js算是必備知識之一。同時因為自己需要做一些后臺性的工作,或者完成一個小型應(yīng)用。所以學習了Node的Express框架,用于輔助和加...
摘要:現(xiàn)在我們從實現(xiàn)一個簡易的方法開始探索其中的機制。其中內(nèi)部的可以將上一個的返回值傳遞給外部。一言以蔽之實現(xiàn)了遞歸調(diào)用的方法。當執(zhí)行到的中間件沒有時并且返回的為時逆序執(zhí)行。 本文發(fā)布在github.com/ssssyoki,歡迎star,issues共同交流。 Koa是基于Node.js的下一代web開發(fā)框架,相比Express更輕,源碼只有幾百行。與傳統(tǒng)的中間件不同,在Koa 1.x中采...
摘要:本筆記共四篇源碼閱讀筆記源碼閱讀筆記源碼閱讀筆記服務(wù)器啟動與請求處理源碼閱讀筆記對象起因前兩天閱讀了的基礎(chǔ),和中間件的基礎(chǔ)。的前端樂園原文鏈接源碼閱讀筆記服務(wù)器啟動與請求處理 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務(wù)器の啟動與請求處理Koa源碼閱讀筆記(4) -- ctx對象 起因 前兩天閱讀了K...
閱讀 1323·2021-11-16 11:45
閱讀 2247·2021-11-02 14:40
閱讀 3888·2021-09-24 10:25
閱讀 3035·2019-08-30 12:45
閱讀 1267·2019-08-29 18:39
閱讀 2479·2019-08-29 12:32
閱讀 1615·2019-08-26 10:45
閱讀 1926·2019-08-23 17:01