第一篇文章中我們講過,“在Koa中,中介軟體是指連貫整個 Koa 應用程式,並共享資源的獨立外掛”,注意兩個詞,“連貫”與“共享資源”,與上面的程式碼一一對應,“連貫”對應“next”,“共享資源對應context”。
Koa 中通過 next 貫穿整個應用程式,下面分析一下 next 中做了什麼。
中介軟體集合
Koa 類中的建構函式中初始化了一堆資料,其中兩個重要的,一個是“middleware”,另一個是“context”。(非關鍵程式碼使用…省略)
constructor() {
...
this.middleware = [];
this.context = Object.create(context);
...
}
所有的中介軟體在一個陣列存放,當我們呼叫“app.use”方法的時候會往陣列中加入我們自定義的中間價
use(fn) {
...
this.middleware.push(fn);
return this;
}
最後通過“koa-compose”來統一觸發中介軟體佇列
callback() {
const fn = compose(this.middleware);
...
return (req, res) => {
...
fn(ctx).then(() => respond(ctx)).catch(ctx.onerror);
};
}
koa-compose
koa-compose 原始碼只有短短几十行,關鍵程式碼不到10行,直接貼上原始碼
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!`)
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
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
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1)
}))
} catch (err) {
return Promise.reject(err)
}
}
}
}
如果我們需要使用 Koa 的洋蔥模型可以直接呼叫 koa-componse 來達到目的
const koaCompose = require(`koa-compose`);
const middleware1 = (ctx, next) => {
console.log(`middleware1 >>>>>`);
next();
console.log(`middleware1 <<<<<`);
}
const middleware2 = (ctx, next) => {
console.log(`middleware2 >>>>>`);
next();
console.log(`middleware2 <<<<<`);
}
const middleware3 = (ctx, next) => {
console.log(`middleware3 >>>>>`);
console.warn(ctx);
next();
console.log(`middleware3 <<<<<`);
}
const fn = koaCompose([middleware1, middleware2, middleware3]);
fn({ a: `a` }, (ctx) => {
console.warn(ctx);
console.warn(`The last next use do =======<`);
return ctx;
}).then((ctx) => {
console.warn(ctx);
console.warn(`end =====<`);
});
輸出:
middleware1 >>>>>
middleware2 >>>>>
middleware3 >>>>>
{ a: `a` }
{ a: `a` }
The last next use do =======<
middleware3 <<<<<
middleware2 <<<<<
middleware1 <<<<<
undefined
end =====<
精簡 koa-componse
為了更好的分析程式碼,去除 koa-componse 程式碼中的各種非關鍵判斷及非同步處理邏輯後,程式碼如下
const compose = function (middlewares) {
// 返回一個函式,提供兩個引數,一個是傳入的上下文,另一個是所有中介軟體執行完畢後回撥
return function(context, last) {
let idx = -1; // 初始定義當前執行中介軟體下標未-1,即表示當前未執行任何中介軟體
return dispatch(0); // 手動觸發第1箇中介軟體
function dispatch(i) {
idx = i; // 設定當前執行中介軟體下標
let fn = middlewares[i] || last;
try {
// 執行當前中介軟體的時候,給當前中介軟體引數中的next引數賦值為一個函式,在這個函式中執行下一個中介軟體
if (fn) fn(context, function() {
dispatch(i + 1); // 觸發下一個中間價,並且將中介軟體執行下標+1
})
} catch (err) { // 所有的中介軟體執行完畢,執行最後回撥
last(context);
}
}
}
};
執行程式碼:
const run = compose([middleware1, middleware2, middleware3]);
run({ a: `a` }, () => {
console.warn(`Middlewares do last =======<`);
});
輸出:
middleware1 >>>>>
middleware2 >>>>>
middleware3 >>>>>
Middlewares do last =======<
middleware3 <<<<<
middleware2 <<<<<
middleware1 <<<<<
總體思路
- 將所有中介軟體推送到一個陣列中
- 第一個中介軟體首先執行
- 在第一個中介軟體執行前,將第一個中介軟體中的 next 引數設定為一個觸發下一個中介軟體執行的函式
- 第一個中介軟體呼叫 next 函式,把執行器交給下一個中間價
- 迴圈往復,直最後一箇中介軟體執行完畢
- 所有中介軟體執行完畢後,依次執行next外層程式碼
優點
- 解決多重回撥地獄模式
- 統一處理上下文context掛載與傳遞
- 異常捕獲
缺點
- 當一個專案中存在多箇中介軟體時,對於效能會有一定影響,對於優化來說是一種考驗