洋葱模型
定义:以next函数为分割点,先由外到里执行request的逻辑,再由里到外执行response的逻辑。也就是说,每一个中间件都会有两次处理时机。
举个例子
const app = new Koa();
app.use((ctx, next) => {
console.log('1');
next();
console.log('1-1');
});
app.use((ctx, next) => {
console.log('2');
next();
console.log('2-2');
});
app.use((ctx, next) => {
console.log('3');
next();
console.log('3-3');
});
// 执行后,控制台打印应该是
1
2
3
3-3
2-2
1-1
在koa中,中间件被next分割成2部分。next上面的部分会被先执行,下面部分会在中间件全部执行结束后再执行,如下图所示:
在洋葱模型当中,每一层相当于一个中间件,用来处理特定的功能,例如错误抓去、session处理。
koa 的实现
use 方法
use 方法只干一件事,就是将中间件函数压入需要执行的数组当中。
function use(fn) {
this.middleware.push(fn);
return this;
}
compose 方法
compose 方法需要将压入完成的中间件数组,通过递归的方式组合执行顺序。先看下建议的实现逻辑
function compose(middlewares) {
return (ctx, next) => {
function dispatch(i) {
const fn = middleware[i];
if (!fn) {
return;
}
return fn(ctx, dispatch.bind(null, i + 1));
}
return dispatch[0];
}
}
- 执行middleware1,执行到next函数
- 执行middleware2,执行到next函数
- 执行middles3,执行到next函数,发现没有下一个中间件,跳过,继续执行
- middleware3执行完毕,即middleware2的next函数执行完毕,继续执行middleware2的剩余代码
- middleware2执行完毕,即middleware1的next函数执行完毕,继续执行middleware1的剩余代码
其实最关键的理解点就是 next 函数就是下一个要执行的中间件!
koa的源码实现
koa是通过这个库 koa-compose 来实现的
/**
* Compose `middleware` returning
* a fully valid middleware comprised
* of all those which are passed.
*
* @param {Array} middleware
* @return {Function}
* @api public
*/
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, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}