koa2源码分析
引言
最近在寫koa2相關例子,順便看了下koa2的源碼,下面是一些個人理解。
koa1核心基于generator,但是嚴重依賴co的包裝。koa2完全不需要,基于async(其實質是generator的語法糖調用包裝),在node v7 下可直接運行。
關于async和generator的語法,本文不做贅述。下面先創建一個koa實例,然后基于入口一步步分析。
說明 上面這段代碼似乎有些神秘,其實質是下面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構造函數入口做進一步分析。
constructor() {super();this.proxy = false;//創建一個空數組存放放middleware,洋蔥流程的真相,下面會分析法到this.middleware = [];//決定忽略的子域名數量,默認為2this.subdomainOffset = 2;//處理環境變量this.env = process.env.NODE_ENV || 'development';//實例上掛載context,request,responsethis.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的主要內涵也就在這,所以做一下重點來論述。
首先引入經典中間件洋蔥圖,以便理解。
結合這幅圖再看下面的代碼
核心代碼application 126 行 const fn = compose(this.middleware);return function (context, next) {let index = -1return dispatch(0)function dispatch (i) {if (i <= index) return Promise.reject(new Error('next() called multiple times'))index = ilet 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);} }如果結合注釋看上述代碼過程中存在疑惑,可進一步參考下面的進行思考,反之忽略即可。
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基于中間價架構,核心簡潔,除此之外還有一些其它相對重要的方法。
application.js
- use(fn) //組裝use的傳參
- createContext(req, res) 創建初始化的上下文,將req和res掛載在context上
- onerror(err) 錯誤處理,當設定this.slient為true,不會輸出信息,在emit觸發時執行
- respond(ctx) http response簡單封裝,信息返回
context.js
- delegate(proto, 'request') //Request相關方法委托,從而讓context作為調用入口
- onerror(err) //中間件執行過程中異常處理邏輯
除此之外還有request和response的參數解析文件,因為邏輯簡單,不做敘述。
雖然核心文件不多,但是其也require了不少包,下面列舉幾個比較重的,以作為示例。
require
- events application繼承自Emitter,從而可以實現事件的訂閱發布。
- koa-compose 中間件的封裝,核心邏輯之一,上面已分析。
- debug 錯誤信息格式封裝處理
- statuses http狀態碼和和相應信息對應處理
- koa-convert 把generator轉為promise
- …… and so on
總結
寫這篇文章起因,是在寫case的過程中,同一解決方案下中間件選擇的糾結癥,尤其是在選擇render template過程中,為找其本質間差異,探尋到此。
源碼分析基于koa(version 2.2.0),通讀源碼之后,可以較清晰的開發或者使用別人的中間件,
如果你有不同的理解,歡迎留言交流。
總結
- 上一篇: css 单行文本溢出显示省略号
- 下一篇: swiper去除滑动设置