在用 PHP 開發自己部落格後臺的時候,碰到一個問題:一般的後臺框架都會提供中介軟體,給使用者自定義額外的功能,so,PHP 應該如何整合一系列的功能中介軟體呢?
你可能想到的實現
註冊中介軟體的時候將其存到陣列中,執行時遍歷陣列。
const app = new App();
// 註冊中介軟體
// this.middlewares.push(middleware);
app.use(middleware);
// 使用中介軟體
// 內部方法實現
function __internal() {
// some injections here.
for (let i = 0; i < this.middlewares.length; i++) {
const fn = this.middlewares[i];
// 你可以往中介軟體注入一些東西
fn(injection);
}
}
複製程式碼
最簡單、最笨的一種方式。來看看其他大佬怎麼做的。
redux 中介軟體
應用了函數語言程式設計盛行的“函式合成(compose
)”。簡而言之就是,將多箇中介軟體函式合成一個函式,最後只執行一次。
redux
中介軟體的註冊方式是一次性的,無需多次呼叫 .use
或其它用於註冊的函式:
applyMiddleware(middleware1, middlwware2, ...);
複製程式碼
來看看 applyMiddleware
實現:
// applyMiddleware.js
// middlewareAPI 是我們注入中介軟體的一些東西
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// compose 所有中介軟體,之後一次性呼叫 dispatch
dispatch = compose(...chain)(store.dispatch)
複製程式碼
compose
在這裡不在贅述。想了解的老鐵們可以去瞅下 阮一峰的函數語言程式設計入門。
koa 中介軟體
將註冊的中介軟體存到 middleware
陣列中,執行時呼叫 compose
之後的函式。
// application.js
class Application {
use(fn) {
// 註冊時
this.middleware.push(fn);
}
callback() {
// compose 成一個 fn 函式呼叫
const fn = compose(this.middleware);
}
}
複製程式碼
koa-compose
與函數語言程式設計概念中的 compose
略有不同,採用“遞迴 + Promise”的方式,形成了其獨特的“洋蔥模型”。
// koa-compose/index.js
return dispatch(0);
function dispatch(i) {
let fn = middleware[i];
// 終止條件
if (!fn) return Promise.resolve();
// 遞迴呼叫
// dispatch 就是往中介軟體注入的 next
// 從這裡我們也可以看到為什麼寫中介軟體時一定要呼叫 next 方法,
// 如果不呼叫的話,遞迴不會繼續,之後的而中介軟體也不執行,一直 pending
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
}
複製程式碼
這樣也很難理解為什麼 await next()
之後的程式碼會反序執行,來看個簡單的例子:
async function foofoo() {
console.log(2);
await Promise.resolve();
console.log(2);
}
async function foo() {
console.log(1);
await foofoo();
console.log(1);
}
a(); // 1 2 2 1
複製程式碼
還是隻可意會,不可言傳[奸笑]。關鍵點在於遞迴。
express 中介軟體
註冊成陣列(額外的例項化成 layer
)形式,呼叫時從第一個開始執行,執行完畢後呼叫 next
找到第二個,第二個執行完後 next
第三個,直至最後。
express
中介軟體的應用其實是到 router
上面,所有我們直接跳到路由的設計:
// router/index.js
// 註冊時
proto.use = function use(fn) {
// 例項化 layer
var layer = new Layer(options, fn);
this.stack.push(layer);
};
// 執行時
proto.handle = function handle(req, res, next) {
// 生成引用。要注意 this 這個坑
var stack = this.stack;
next();
// 這個 next 就是我們注入了中介軟體裡
function next(err) {
while (match !== true && idx < stack.length) {
layer = stack[idx ++];
match = matchLayer(layer, path);
// 如果你呼叫 next(1) 的話,這裡會一直不匹配!
// 最後直接跳過後面的中介軟體返回 1
if (layerError) {
match = false;
continue;
}
}
// 找到後
// 其實內部也就是執行我們的 middleware 函式
return layer.handle_request(req, res, next);
}
};
複製程式碼
跟 for 迴圈很像,但是又依賴於開發者手動 next
觸發下一個。
so ?
講了半天中介軟體,和我要的 compose
有半毛錢關係哦。
redux
用到了精髓koa
有點皮毛東西express
八竿子打不著
強行 pick 一波:如果你還在古老地迴圈呼叫一系列函式,不妨 compose
試下。