Koa 2 中间件洋葱模型实现

koa被认为是下一代的web框架,其最大的特点就是独特的中间件控制,是一个典型的对洋葱模型的实现。其中koa和koa2的中间件实现思路是一样的,只是具体实现有所区别,koa2在node7.6之后使用async/await替代generator函数,本文以async/await来实现洋葱模型。

洋葱模型

借来一张网上的图片来说明下,洋葱模型主要分两个过程——“出”和“入”,对于“入”方向先处理的中间件,在“出”方向时就会变成后处理,这样的好处是同一个中间件可以同时处理两个过程,而且不用在意顺序。在koa中,前置中间件就可以处理入方向的req请求和出方向的res结果,其处理逻辑类似于python平台中的django框架的中间件。

代码实现

实现思路就是运用async/await函数,因为await关键字支持promise,可以在promise没有返回的时候暂停执行后面的代码。

基于这个原理,如果传入的一组函数都是async函数,并且以某种手段让后面的函数作为前一个函数的await表达式,那么我们不就可以实现洋葱模型了么?

const md1 = async (next) => {
  console.log('gen1 start')
  await next()
  console.log('gen1 end')
}
const md2 = async (next) => {
  console.log('gen2 start')
  await next()
  console.log('gen2 end')
}
const md3 = async (next) => {
  console.log('gen3 start')
  await next()
  await new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('gen3 end')
      resolve()
    }, 2000)
  })
}
const stack = [md1, md2, md3]
const noop = async () => {
  console.log('noop')
}
const call = () => {
  let index = -1

  function dispatch (i) {
    index = i
    let fn = stack[i]
    if (i === stack.length) fn = noop
    if (!fn) return Promise.resolve()
    try {
      return Promise.resolve(fn(() => dispatch(i + 1)))
    } catch (err) {
      return Promise.reject(err)
    }
  }

  return dispatch(0)
}

call().then(() => {
  console.log('stack end')
}).catch(err => {
  console.log(err)
})