在用 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
试下。