用法回顧
const Koa = require(`koa`);
const app = new Koa();
app.use(async (ctx, next) => {
fn1();
await next();
fn2();
});
app.use(async (ctx, next) => {
fn3();
await next();
fn4()
});
app.use(async (ctx, next) => {
fn5();
await next();
fn6()
});
app.listen(3000);
執行順序 fn1 -> fn3 -> fn5 -> fn6 -> fn4 -> fn2
每當執行next時,執行下一個中介軟體,執行到最後一箇中介軟體後開始往回執行
原始碼解析
原始碼執行步驟
- 使用use方法即將middleware push 進koa中的this.middleware陣列中
- listen方法呼叫node的http.createServer和server.listen方法來建立服務,createServer的回掉執行下面的操作
- 回掉首先執行compose(this.middleware)方法將middleware組合成一個promise物件來執行,這個promise物件即可完成中介軟體級聯的操作
- 將回掉傳出的值req和res用於建立一個context物件,裡面包含了request物件和response物件,這些物件提供了許多後臺開發需要的引數和方法
- 執行中介軟體
- 得到結果給respond方法格式化資料
- 或者捕獲異常給onerror方法處理異常
compose
koa原始碼最重要的部分,如何實現中介軟體級聯,以下是compose方法的原始碼(為方便觀看,部分進行了ES6處理)
function compose (middleware) {
if (!Array.isArray(middleware)) throw new TypeError(`Middleware stack must be an array!`)
for (const fn of middleware) {
if (typeof fn !== `function`) throw new TypeError(`Middleware must be composed of functions!`)
}
return function (context, next) {
// last called middleware #
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 // next = undefined
if (!fn) return Promise.resolve() // 當執行到最後一個middleware時結束
try {
return Promise.resolve(
fn(context, next = () => dispatch(i + 1))
)
} catch (err) {
return Promise.reject(err)
}
}
}
}
- compose方法使用遞迴的方式遍歷每一個middleware
- 遍歷時將下一個middleware當作next傳遞給當前的middleware