成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

從Generator開始學習Koa

doodlewind / 3507人閱讀

摘要:需要說明的是,每次執(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}

這個返回值有兩個屬性:valuedone,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)該也不會太難。

理解了洋蔥模型,就不難明白,yieldPromise在其中所起的作用了。

關(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

相關(guān)文章

  • 深入探析koa之中間件流程控制篇

    摘要:到此為止,我們就基本講清楚了中的中間件洋蔥模型是如何自動執(zhí)行的。 koa被認為是第二代web后端開發(fā)框架,相比于前代express而言,其最大的特色無疑就是解決了回調(diào)金字塔的問題,讓異步的寫法更加的簡潔。在使用koa的過程中,其實一直比較好奇koa內(nèi)部的實現(xiàn)機理。最近終于有空,比較深入的研究了一下koa一些原理,在這里會寫一系列文章來記錄一下我的學習心得和理解。 在我看來,koa最核心...

    fuchenxuan 評論0 收藏0
  • 零組裝新工具 - Koa2

    摘要:返回后,代表操作已完成,記錄結(jié)束時間并輸出。從零組裝因為對的學習和使用,知道了自己對于后臺框架的真實需求。所以這回決定不用之內(nèi)的工具,而是自己從零開始,組裝一個適合自己的框架。就是去和上,尋找一個一個的包并組裝在一起了而已。 起因 作為一個前端,Node.js算是必備知識之一。同時因為自己需要做一些后臺性的工作,或者完成一個小型應(yīng)用。所以學習了Node的Express框架,用于輔助和加...

    sutaking 評論0 收藏0
  • Koa-middleware實現(xiàn)機制的分析

    摘要:現(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中采...

    MageekChiu 評論0 收藏0
  • Koa源碼閱讀筆記(3) -- 服務(wù)器の啟動與請求處理

    摘要:本筆記共四篇源碼閱讀筆記源碼閱讀筆記源碼閱讀筆記服務(wù)器啟動與請求處理源碼閱讀筆記對象起因前兩天閱讀了的基礎(chǔ),和中間件的基礎(chǔ)。的前端樂園原文鏈接源碼閱讀筆記服務(wù)器啟動與請求處理 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務(wù)器の啟動與請求處理Koa源碼閱讀筆記(4) -- ctx對象 起因 前兩天閱讀了K...

    mrcode 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<