技术咨询、项目合作、广告投放、简历咨询、技术文档下载
点击这里 联系博主
# KOA 中间件 洋葱模型分析
在进行洋葱模型分析前先了解一下什么是洋葱模型; 比如有如下的代码:
const Koa = require("koa")
const app = new Koa();
// 中间件1
app.use(async (ctx, next) => {
console.log("1")
await next()
console.log("2")
});
// 中间件2
app.use(async (ctx, next) => {
console.log("3")
await next()
console.log("4")
});
// 中间件3
app.use(async (ctx, next) => {
console.log("5")
await next()
console.log("6")
});
app.listen(8002);
那么代码执行的顺序应该是
1
3
5
6
4
2
通过可视化方式展示应该是这样的
起koa 实现 整个 中间件的核心是使用的 koa-compose (opens new window)库;
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
function dispatch (i) {
// 注意点3:防止一个中间件调用多个next函数
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
// 注意点2:防止数组超界
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
// 注意点1:递归调用下一个 对应的中间件函数
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
} catch (err) {
return Promise.reject(err)
}
}
// 开始调用第一个中间件函数
return dispatch(0)
}
}
上面提到三个注意点,我们逐步来分析一下:
# 注意点1: 使用 i + 1的方式 递归调用 下一个中间件函数
如果有如下的代码 那么
- 首先会 执行 第一个中间件(dispatch(0));
- 输出 log 1
- 执行next: 因为next 是
dispatch.bind(null, i + 1)
所以其实执行的是第二个 中间件函数 - 输出 log 3
- 输出 log 4
- 输出 log 2
const app = new Koa();
// 中间件1
app.use((ctx, next) => {
console.log('1');
next();
console.log('2');
});
// 中间件2
app.use((ctx, next) => {
console.log('3');
console.log('4');
});
# 注意点2: 防止数组超界
如果存在如下代码;
- 首先会 执行 第一个中间件(dispatch(0));
- 输出 log 1
- 执行 next: 因为next 是
dispatch.bind(null, i + 1)
所以其实执行的是第二个 中间件函数 - 输出 log 3
- 执行 next: 因为next 是
dispatch.bind(null, i + 1)
此时 i = 2; 但是现在一共只有 2个中间件,没有3个,所以 需要 加上 数组越界判断
const app = new Koa();
// 中间件1
app.use((ctx, next) => {
console.log('1');
next();
console.log('2');
});
// 中间件2
app.use((ctx, next) => {
console.log('3');
next();
console.log('4');
});
# 注意点3: 防止一个中间件调用多个next函数
通过分析如下的代码 我们来了解为什么 可以通过数组 下标去 防止多个next函数;
注意 index 的作用域 > i; 因为 i 是dispatch 的局部变量
- 首先会 执行 第一个中间件(dispatch(0)): 此时 index = -1 , i = 0
- 输出 log 1; 此时由于 index = i; 所以 index = 0; i = 0
- 执行 next: 因为next 是
dispatch.bind(null, i + 1)
所以其实执行的是第二个 中间件函数: 此时 index = 0; i = 1 - 输出 log 3; 此时由于 index = i; 所以 index = 1; i = 1
- 执行 next: 因为next 是
dispatch.bind(null, i + 1)
所以其实执行的是第三个 中间件函数: 此时 index = 1; i = 2 - 输出 log 5; 此时由于 index = i; 所以 index = 2; i = 2
- 输出 log 6; 由于同log 5在同一个函数 所以 index = 2; i = 2
此时 index = i = 中间件的数量;
由于 每一个中间件 执行 内部 的 i 是局部变量,依然保持着 0 ,1 , 2; 所以在执行 log 4 , log 2 之前 如果 还要 执行 next 则 i 分别 为 1 和 0
如果判断 i(1/0)<= index(2) 的话;就能有效的阻止 log 4 和 log 2 之前 再次调用 next 函数
const app = new Koa();
// 中间件1
app.use((ctx, next) => {
console.log('1');
next();
next();
console.log('2');
});
// 中间件2
app.use((ctx, next) => {
console.log('3');
next();
console.log('4');
});
// 中间件3
app.use((ctx, next) => {
console.log('5');
console.log('6');
});
app.listen();
- 本文链接: https://mrgaogang.github.io/nodejs/koa-compose.html
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 许可协议。转载请注明出处!