koa-洋葱模型

1,102 阅读2分钟

之前面试腾讯时,面试官问了我一个问题,“你知道koa的洋葱模型吗?”

我愣了一下,“不知道,没了解过”

虽然我面试的是前端实习,但是面试官从头到尾都在问我后端的问题,我不是很能理解,但是既然被问了这个问题,那么我们就必须在下一次被问的时候能和面试官说道说道

所以我去阅读了koa的源码,然后写下了这篇文章

这篇文章主要讲的是洋葱模型的原理,其他未涉及,我默认读者使用过koa

洋葱

先看下面这段代码,我创建了一个app,然后添加了三个中间件

const Koa = require('koa');

const app = new Koa();

app.use(async (ctx, next)=>{
    console.log(1)
    await next();
    console.log(1)
});
app.use(async (ctx, next) => {
    console.log(2)
    await next();
    console.log(2)
})
app.use(async (ctx, next) => {
    console.log(3)
})

app.listen(3000);

通过访问localhost:3000之后,控制台的输出是这样的

1
2
3
2
1

这个现象可以用下面这张图来描述

IMG_311318E912A4-1

让我给他加上外壳

IMG_0DC014C07D19-1

有没有觉得很像一颗洋葱!?

是的,这个就是koa的洋葱模型

他的执行过程如图中所展现的那样,从外面一层一层的进去,再一层一层的从里面出来,这个也是koa中间件的执行过程,相信看到这里,你应该已经知道了koa中间件的执行过程,下面是洋葱模型的实现代码

原理

在koa的源码中,中间件们被存储在一个数组中

let middleware = [];

我们可以通过use(...)添加中间件,主要逻辑大概这样

const use = (fn)=>{
  middleware.push(fn)
}

而调用中间件的是compose这个函数,下面的代码我做了详细的解释

function compose (middleware) {
  return function (context, next) {
    // index的作用:记录当前的已经执行过的中间件个数
    let index = -1
    return dispatch(0)
    // i的作用:当前要执行的中间件的下标
    function dispatch (i) {
      // 如果i<=index 意味着 这是第二遍执行next函数,抛出异常
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      // 更新index,对应上面
      index = i
      // 提取出当前需要执行的中间件
      let fn = middleware[i]
      // 执行compose的回调函数
      if (i === middleware.length) fn = next
      // 不存在则意味着到了最后一个中间件,直接返回
      if (!fn) return Promise.resolve()
      // 此处try的作用,是保证出现异常时可以传递异常并返回
      try {
        // 将dispatch传递下去,作为中间件的next函数,形成递归调用
        // 可以发现此处i+1了,即调用的是下一个中间件
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}