最近在寫koa2相關例子,順便看了下koa2的源碼,下面是一些我的理解。node
koa1核心基於generator,可是嚴重依賴co的包裝。koa2徹底不須要,基於async(其實質是generator的語法糖調用包裝),在node v7 下可直接運行。
關於async和generator的語法,本文不作贅述。下面先建立一個koa實例,而後基於入口一步步分析。web
//koa code const Koa=require('koa'); const app=new Koa(); app.use(async function (ctx, next) { console.log('>> one'); await next(); console.log('<< one'); }); app.use(ctx => { ctx.body='hello world gcy'; }); app.listen('3000',function () { console.log('listening on port 3000'); });
說明 上面這段代碼彷佛有些神祕,其實質是下面http module的封裝調用。數組
// native code let http=require('http'); let server=http.createServer(function (req,res) { res.writeHead(200,{'Content-type':'text/plain'}); res.write('hello world gcy'); res.end(); }); //start service listen server.listen(8000,function () { console.log('listening on port 8000'); });
下面基於koa構造函數入口作進一步分析。promise
constructor() { super(); this.proxy = false; //建立一個空數組存放放middleware,洋蔥流程的真相,下面會分析法到 this.middleware = []; //決定忽略的子域名數量,默認爲2 this.subdomainOffset = 2; //處理環境變量 this.env = process.env.NODE_ENV || 'development'; //實例上掛載context,request,response this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); }
說明 上面是構造函數入口,啓動入口以下服務器
//借用原生http.createServer,添加app.callback。 listen() { debug('listen'); const server = http.createServer(this.callback()); return server.listen.apply(server, arguments); }
說明 經過上面兩個步驟一個完整的web服務器創建起來。對於監聽接受到的請求解析處理,是經過callback函數,調用一系列中間件來完成。
下面分析中間件執行流程,我認爲koa的主要內涵也就在這,因此作一下重點來論述。
首先引入經典中間件洋蔥圖,以便理解。架構
結合這幅圖再看下面的代碼app
////核心代碼application 126 行 const fn = compose(this.middleware); return function (context, next) { let index = -1 return dispatch(0) function dispatch (i) { if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i let fn = middleware[i] if (i === middleware.length) fn = next //當即返回處於resolve狀態promise實例,以便後續邏輯繼續執行 if (!fn) return Promise.resolve() try { // await next(); //當fn裏面執行這句話時,就會執行dispatch(i+1),致使洋蔥執行過程 // 整個過程相似堆棧執行釋放過程當中的的遞歸調用,雖然有差別,可借用類比思考其執行順序流程 return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } // 核心代碼application 136 行 return fn(ctx)[是一個當即狀態的promise].then(handleResponse).catch(onerror); } }
若是結合註釋看上述代碼過程當中存在疑惑,可進一步參考下面的進行思考,反之忽略便可。dom
const Koa=require('koa'); const app=new Koa(); app.use(async function (ctx, next) { console.log('>> one'); await next(); console.log('<< one'); }); app.use(async function (ctx, next) { console.log('>> two'); ctx.body = 'two'; await next(); console.log('<< two'); }); app.use(async function (ctx, next) { console.log('>> three'); await next(); console.log('<< three'); }); //若是放到首部,不方便理解洋蔥執行流程,由於沒有調用next函數 app.use(ctx => { ctx.body='hello world gcy'; }); app.listen('3000',function () { console.log('listening on port 3000'); });
說明 koa基於中間價架構,核心簡潔,除此以外還有一些其它相對重要的方法。koa
除此以外還有request和response的參數解析文件,由於邏輯簡單,不作敘述。
雖然核心文件很少,可是其也require了很多包,下面列舉幾個比較重的,以做爲示例。async
寫這篇文章原由,是在寫case的過程當中,同一解決方案下中間件選擇的糾結症,尤爲是在選擇render template過程當中,爲找其本質間差別,探尋到此。源碼分析基於koa(version 2.2.0),通讀源碼以後,能夠較清晰的開發或者使用別人的中間件,若是你有不一樣的理解,歡迎留言交流。