本系列是關於Koa框架的文章,目前關注版本是Koa v1。主要分爲如下幾個方面:html
Koa是基於generator與co之上的新一代中間件框架,它的優點主要集中在如下幾個方面node
var Koa = require('koa'); var app = new Koa(); //添加中間件1 app.use(function *(next){ var start = new Date; console.log("start=======1111"); yield next; console.log("end =======1111"); var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); }); //添加中間件2 app.use(function *(){ console.log("start=======2222"); this.body = 'Hello World'; console.log("end =======2222"); }); app.listen(3000); /* start=======1111 start=======2222 end =======2222 end =======1111 GET / - 10 start=======1111 start=======2222 end =======2222 end =======1111 GET /favicon.ico - 5 */
從上述代碼中,咱們添加了兩個middleware,其中第一個middleware中有一個輸入參數next
,並經過yield
進行調用。經過分析輸出的log信息,不難發現,先運行middelware1中的yield
以前的代碼,而後進入到middleware2中運行,待middleware2運行結束後又回到middleware1中,並運行yield
以後的代碼。編程
因爲app.use
輸入的是generator函數,若是熟悉generator函數的同窗,或許會說,這是將middleware2做爲middleware1中的next參數,依次調用多個generator函數。對,沒錯,實際運行就是這樣的,可是koa框架是如何組織代碼實現這樣方面的調用,將地獄式調用的異步編程編程這樣清晰的結構?請看下文的源碼分析數組
function Application() { if (!(this instanceof Application)) return new Application; this.env = process.env.NODE_ENV || 'development'; this.subdomainOffset = 2; // 用於存放中間件,即generator對象 this.middleware = []; this.proxy = false; // 得到封裝的上下文對象 this.context = Object.create(context); // 獲取封裝的請求對象 this.request = Object.create(request); // 獲取封裝的響應對象 this.response = Object.create(response); }
listen() { debug('listen'); // 調用node原生中的建立服務 // 其中callback()是服務建立的核心,具體見下面分析 const server = http.createServer(this.callback()); // 開啓服務的監聽 return server.listen.apply(server, arguments); }
app.use = function(fn){ if (!this.experimental) { // es7 async functions are not allowed, // so we have to make sure that `fn` is a generator function assert(fn && 'GeneratorFunction' == fn.constructor.name, 'app.use() requires a generator function'); } debug('use %s', fn._name || fn.name || '-'); // 將輸入的fn依次push到middleware數組中 this.middleware.push(fn); // 返回this,以便鏈式調用 return this; };
app.callback = function(){ if (this.experimental) { console.error('Experimental ES7 Async Function support is deprecated. Please look into Koa v2 as the middleware signature has changed.') } // 將中間件按照加入的順序,實現yield的鏈式調用,即組織異步調用結構,詳細見下面的compose // co.wrap方法將generator函數轉化爲Promise var fn = this.experimental ? compose_es7(this.middleware) : co.wrap(compose(this.middleware)); var self = this; if (!this.listeners('error').length) this.on('error', this.onerror); // 返回node native的請求處理函數 return function handleRequest(req, res){ res.statusCode = 404; var ctx = self.createContext(req, res); onFinished(res, ctx.onerror); fn.call(ctx).then(function handleResponse() { respond.call(ctx); }).catch(ctx.onerror); } };
// 返回一個啓動函數 function compose(middleware){ return function *(next){ if (!next) next = noop(); var i = middleware.length; // 對中間件隊列從後遍歷,逐個獲取對應的generator對象 while (i--) { // 將後面的generator對象傳遞給前面中間件的generatorFunction next = middleware[i].call(this, next); } // 返回一個yield,next指向第一個中間件的generator return yield *next; } } function *noop(){}
這樣,咱們就從返回的啓動函數(generator函數)的yield處指向第一個中間件,而後從以前while
循環構成的從前日後的調用鏈,依次調用下一個中間件,直至最後一箇中間件而後再返回。app
這邊咱們再次回到callback()
這個啓動函數處,調用co.wrap()
實現對generator函數的逐步調用。框架