學習Koa框架以前,不得不提到Express。javascript
Express
是一個基於Node.js平臺的極簡、靈活的 web 應用開發框架,主要基於 Connect 中間件,而且自身封裝了路由、視圖處理等功能,使用人數衆多。java
Koa
相對更爲年輕,是 Express 原班人馬基於 ES6/7 異步流程控制從新開發的框架, 框架自身不包含任何中間件,不少功能須要藉助第三方中間件解決,解決了回調地獄和麻煩的錯誤處理問題。node
async/await
,koa1實現異步是經過generator + co
,而express實現異步是經過回調函數
的方式。咱們主要學習Koa2,首先看一下它的源碼結構: es6
koa2的核心文件:application.js
、
context.js
、
request.js
、
response.js
。
application.js:Application(或Koa)負責管理中間件,以及處理請求web
koa的入口文件
,它向外導出了建立class實例的構造函數,它繼承了events
,這樣就會賦予框架事件監聽和事件觸發
的能力。 application還暴露了一些經常使用的api,好比toJSON、listen、use等等。傳入的callback
,它裏面包含了中間件的合併,上下文的處理,對res的特殊處理。context.js:Context維護了一個請求的上下文環境express
重點在delegate
,這個就是代理,這個就是爲了開發者方便而設計的。request.js:Request對req
作了抽象和封裝api
response.js:Response對res
作了抽象和封裝數組
這兩部分就是對原生的res、req的一些操做了,大量使用es6的get和set的一些語法,去取headers或者設置headers、還有設置body等等。promise
koa框架須要實現四個大模塊,分別是:緩存
經過application.js
源碼得知,koa的服務器應用和端口監聽,其實就是基於node的http.createServer
原生代碼進行了封裝。咱們來簡單實現一下這個功能。
let http = require('http');
class Application{
constructor() {
this.callbackFunc = ()=>{};
}
//開啓服務器實例並傳入callback回調函數
listen(port) {
let server = http.createServer(this.callback());
server.listen(port);
}
//註冊中間件和註冊回調函數
use(fn) {
this.callbackFunc = fn;
}
callback() {
return (req, res) => {
this.callbackFunc(req, res);
};
}
}
module.exports = Application;
複製代碼
而後建立demo.js,引入application.js
let Koa = require('./application');
let app = new Koa();
app.use((req, res) => {
res.writeHead(200);
res.end('hello world');
});
app.listen(3000, () => {
console.log('listening on 3000');
});
複製代碼
request、response兩個功能模塊分別對node的原生request、response進行了一個功能的封裝,使用了getter和setter屬性
,基於node的對象req/res對象封裝koa的request/response對象。
request.js
封裝了query、header、url、origin、path等,好比ctx.query就是返回url.parse(this.req.url, true).query。response.js
封裝了status、body、message等,好比ctx.status就是返回res.statusCode。context.js
再將request和response掛載到了ctx上。let http = require('http');
let context = require('./context');
let request = require('./request');
let response = require('./response');
createContext(req, res) {
let ctx = Object.create(this.context);
ctx.request = Object.create(this.request);
ctx.response = Object.create(this.response);
ctx.req = ctx.request.req = req;
ctx.res = ctx.response.res = res;
return ctx;
}
複製代碼
koa的中間件機制是洋蔥模型
,多箇中間件經過use放進一個數組隊列,符合先進後出
的原則。而後從外層開始執行,遇到next後進入隊列中的下一個中間件,全部中間件執行完後開始回幀,執行隊列中以前中間件中未執行的代碼部分。
示例:
const Koa = require('koa');
const app = new Koa();
//#1
app.use(async (ctx, next) => {
console.log('中間件 1 進入');
await next();
console.log('中間件 1 退出');
});
//#2
app.use(async (ctx, next) => {
console.log('中間件 2 進入');
await next();
console.log('中間件 2 退出');
});
//#3
app.use(async (ctx, next) => {
console.log('中間件 3');
});
app.listen(3000);
複製代碼
訪問http://localhost:3000,控制檯打印:
中間件 1 進入
中間件 2 進入
中間件 3
中間件 2 退出
中間件 1 退出
複製代碼
當程序運行到await next()的時候就會暫停當前程序,進入下一個中間件,處理完以後纔會仔回過頭來繼續處理。 也就是說,當一個請求進入,#1會被第一個和最後一個通過,#2則是被第二和倒數第二個通過,依次類推。
經過use傳進來的中間件是一個回調函數,回調函數的參數是ctx上下文和next,next其實就是控制權的交接棒,next的做用是中止運行當前中間件,將控制權交給下一個中間件, 執行下一個中間件的next()以前的代碼,當下一箇中間件運行的代碼遇到了next(),又會將代碼執行權交給下下箇中間件,當執行到最後一箇中間件的時候,控制權發生反轉, 開始回頭去執行以前全部中間件中剩下未執行的代碼,當最終全部中間件所有執行完後,會返回一個Promise對象,由於咱們的compose函數返回的是一個async的函數,async函數執行完後會返回一個Promise。
function _createNext(middleware, oldNext) {
return async () => {
await middleware(ctx, oldNext);
}
}
compose() {
return async (ctx) => {
let next = async () => {
return Promise.resolve();
};
for (let i = this.middlewares.length - 1; i >= 0; i--) {
let currentMiddleware = this.middlewares[i];
next = _createNext(currentMiddleware, next);
}
await next();
};
}
複製代碼
錯誤處理和捕獲,分爲中間件的異常
與Koa框架框架層的異常
。
前者在application.js
的callback
方法中,根據es7的規範知道,async返回的是一個promise的對象實例,咱們若是想要捕獲promise的錯誤,只須要使用promise的catch方法,就能夠把全部的中間件的異常所有捕獲到。 相似這樣:
return fn(ctx).then(respond).catch(onerror);
複製代碼
後者,經過將koa的構造函數繼承events模塊,使其具有事件監聽on函數和事件觸發emit行爲的能力。
let EventEmitter = require('events');
class Application extends EventEmitter {}
複製代碼
這樣,當咱們建立koa實例的時候,就能夠加上on監聽函數。
let app = new Koa();
app.on('error', err => {
console.log('error happends: ', err.stack);
});
複製代碼