Koa洋葱模型以及简单实现

155 阅读2分钟

koa使用洋葱模型

koa是由Express原班人马打造的轻量级的node框架,koa使用中间件机制,避免了重复繁重的函数回调。其中间件的调用和执行,正是基于洋葱模型。下面代码,反映出koa是如何调用执行中间件的:

const Koa = require('koa')
const app = new Koa()
// 中间件A
app.use(async (ctx, next) => {
  console.log('A start')
  await next()
  console.log('A end')
})
// 中间件B
app.use(async (ctx, next) => {
  console.log('B start')
  await next()
  console.log('B end')
})
// 中间件C
app.use(async (ctx, next) => {
  console.log('C start')
  await next()
  console.log('C end')
})

app.use(async (ctx, next) => {
  console.log('hello')
  ctx.response.body = 'hello'
})
app.listen(3000)

上面的代码片段,node执行并在浏览器打开urlhttp://localhost:3000/后,控制台打印的结果是: A start -> B start -> C start -> hello -> C end -> B end -> A end 。其执行过程,像极了洋葱圈,所以被称为“洋葱模型”,每个洋葱圈是一个中间件, 中间件就是异步函数,其两个参数含义如下:

  • ctx,koa的执行上下文,是对node的request和response的封装
  • next,返回值是Promise的函数,触发下一个中间件的进入和执行

koa的每个请求的执行过程,即中间件的加载和执行,不同中间件可处理不同的事务;中间件的执行,有下面特点

  1. 遇到await next(),立即进入下一个中间件;
  2. 所有的中间件,已经被“进入过”,再回过头来执行next;
  3. “先进后出”,先进入的中间件,next是后执行的;

koa的洋葱模型是如何实现的?

  • app.use,是加载和保存一个中间件。所以,可以使用一个数组保存所有已经被加载的中间件;
  • koa-compose模块,实现了所有中间件的调度执行;遇到next(),即进入下一个中间件;所有中间件都已进入,再执行next的结果
let _app = {}
_app.middleware = []

_app.use = function (fn) {
  this.middleware.push(fn)
}

function compose(middleware) {
  function dispatch(index) {
    let fn = middleware[index]
    if(!fn) {
      return Promise.resolve()
    }
    try {
      return Promise.resolve(fn(() => {
        return dispatch(index + 1)
      }))
    } catch(err) {
      return Promise.reject(err)
    }
  }
  return dispatch(0)
}

// 测试
_app.use(async (next) => {
  console.log('111')
  await next()
  console.log('111 end')
})

_app.use(async (next) => {
  console.log('222')
  await next()
  console.log('222 end')
})

compose(_app.middleware)

为方便演示洋葱模型的执行逻辑,这里的中间件实现,并没有加入ctx;

  1. Promise.resolve(fn(() => { return dispatch(index+1) })),使得中间件函数fn立即执行;
  2. fn中间件的执行,当遇到 await next(),会触发 dispatch(index+1),至此,新的中间件开始执行,重复步骤1
  3. 当middleware数组被遍历执行完,程序终止;