洋葱模型如下图:
对middleware1
而言,其next
就是一个函数,返回middleware2
的执行结果(一个promise对象),同理middleware2
的next
就是一个函数,其返回middleware3
的执行结果(又是一个pormise对象)…以此类推。
用函数表示为:middleware1(()=>middleware2(()=>middleware3(...)))
,类似函数式编程中的compose
,同步情况下的简单写法就是这样:
function compose(middlewares){
// 期望返回一个层层包裹的middleware函数
// 这个函数接收一个next函数
return middlewares.reduce((last, cur) => next => last(() => cur(next)));
}
// 这样就完成了一个同步的模型,测试一下
compose([
function(next) {
console.log("before a");
next();
console.log("after a");
},
function(next) {
console.log("before b");
next();
console.log("after b");
}
])(() => {});
/**
before a
before b
after b
after a
*/
基于上面这个基本版,稍加改造处理一些边界情况即可:
function compose(middlewares) {
if (!Array.isArray(middlewares)) throw new TypeError('Middlewares must be an array!');
middlewares.forEach(item => {
if (typeof item !== 'function') throw new TypeError('Middleware must be componsed of function');
});
const noop = () => {};
// next=noop work for 0 middleware
const emptyMiddleware = async (_, next = noop) => next();
// 保证在一个中间件中next只被调用一次
const guard = ctx => next => {
let runed = false;
// next 返回promise,包装一层依然要返回promise
return async () => {
if (runed) throw new Error('next() should not be called multiple times in one middleware!');
runed = true;
// 这里保证 middlewareChain(ctx,middleware) 正常
return next(ctx, noop);
};
};
return middlewares.reduce(
// middlewareChain可能没被传入next
(last, cur) => (ctx, next = noop) =>
// 不看guard的话很好理解 last(ctx,() => cur(ctx,next))
// 白话就是 前一个middleware 的参数为一个函数 返回当前middleware的执行结果
// 而当前middleware的执行需要传入next
last(
ctx,
guard(ctx)(() => cur(ctx, guard(ctx)(next)))
),
emptyMiddleware
);
}
使用:
compose([
async (ctx,next)=>{
console.log('before a')
await next()
console.log('after a')
},
async (ctx,next)=>{
console.log('before b')
await next()
console.log('after b')
}
])()
/**
before a
before b
after b
after a
*/
已跑通全部用例: https://github.com/koajs/compose/blob/master/test/test.js