技术咨询、项目合作、广告投放、简历咨询、技术文档下载 点击这里 联系博主

# 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();

【未经作者允许禁止转载】 Last Updated: 2/4/2024, 6:06:40 AM