每天进步一点点

koa中间件原理分析

先说一下啥是aop, aop 就是 Aspect Oriented Programming 面向切面编程,java中的spring最大的卖点就是ioc 和 aop, 然而好的东西大家都会去借鉴,应该说任何一个后端框架都有aop。koa的中间件原理被称为洋葱模型

aop有哪些好处

  • 代码复用:封装成中间件,可以服用,没啥毛病
  • 解耦:有了中间件,可以分层,没啥毛病
  • 集中管理:中间件干一样的事情,各司其职,没啥毛病

分析

const Koa = require('koa')


const app = new Koa()

// logger
app.use(async (ctx,next) => {
    await next()
    const rt = ctx.response.get('X-Response-Time')
    console.log(`${ctx.method} ${ctx.url} - ${rt}`)
})

// x-response-time
app.use(async (ctx,next) => {
    const start = Date.now()
    await next()
    const ms = Date.now() - start
    ctx.set('X-Response-Time', `${ms} ms`)
})
app.use(ctx => {
    // console.log(ctx)
    ctx.body = 'hello word'
})
app.listen(3000, () => {
    console.log('run in 3000')
})

访问localhost:3000

这个好像看不出来什么
再来一个栗子

const Koa = require('koa')
const app = new Koa()
const middleware = async (ctx, next)=>{
    console.log(1)
    await next()
    console.log(6)
}
const middleware2 = async (ctx, next)=> {
    console.log(2)
    await next()
    console.log(5)
}
const middleware3 = async (ctx, next) => {
    console.log(3)
    await next()
    console.log(4)
}
app.use(middleware)
app.use(middleware2)
app.use(middleware3)
app.use(ctx => {
    ctx.body = 'xx'
})
app.listen(3000, ()=> {
    console.log('run in 3000')
})



有点感觉了没
去翻一下源码

  use(fn) {
    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
    if (isGeneratorFunction(fn)) {
      deprecate('Support for generators will be removed in v3. ' +
                'See the documentation for examples of how to convert old middleware ' +
                'https://github.com/koajs/koa/blob/master/docs/migration.md');
      fn = convert(fn);
    }
    debug('use %s', fn._name || fn.name || '-');
    this.middleware.push(fn);
    return this;
  }

从这里可以看出,调用use的时候会把中间件存起来,
下面我们来看看app.listen

 listen(...args) {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen(...args);
 }

listen 又调用了 callback,我们再来看看callback

callback() {
    const fn = compose(this.middleware);
    if (!this.listenerCount('error')) this.on('error', this.onerror);
    const handleRequest = (req, res) => {
      const ctx = this.createContext(req, res);
      if (!this.ctxStorage) {
        return this.handleRequest(ctx, fn);
      }
      return this.ctxStorage.run(ctx, async() => {
        return await this.handleRequest(ctx, fn);
      });
    };
    return handleRequest;
  }

callback 把 中间件传进了compose 函数里边,然后构建了context,去看一下compose

function compose (middleware) {
  return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      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)
      }
    }
  }
}

找到关键了, koa的中间件原理就是这个函数,把这个函数拿下,就能理解koa的洋葱模型了,这里的compose 函数,经过了简化,把一些错误校验去掉了,不影响核心逻辑
通过观察可以看出来,这是个递归,还是个尾递归,还是个异步递归
这段代码的核心在于next的实现。next需要依次调用下一个middleware,当到最后一个的时候结束,这样后面middleware的promise先resolve,然后直到第一个,这样的流程也就是洋葱模型的流程了

posted on 2024-04-24 14:21  柯蓝僧人  阅读(42)  评论(0)    收藏  举报