Skip to content

koa 的中间件模型,通常称作洋葱模型

koa 的源码中,koa 中间件串联是基于koa-compose

我们可以自定义代码来模拟实现洋葱模型

js
const app = {
  middlewares: [],
  use (fn) {
    this.middlewares.push(fn)
  },
  compose (middlewares) {
    let index = 0
    // 返回函数
    return function (ctx) {
      // dispatch是一个返回promise的函数
      function dispatch (index) {
        // middleware是一个返回promise的函数
        const middleware = middlewares[index]
        // next也是一个返回promise的函数
        const next = index < middlewares.length - 1 ? () => dispatch(++index) : () => Promise.resolve()
        return middleware(ctx, next)
      }
      // 返回promise
      return dispatch(index)
    }
  },
  listen (port) {
    const promiseFn = this.compose(this.middlewares)
    const ctx = {
      query: {
        name: 'test'
      },
      body: 'Hello'
    }
    promiseFn(ctx).then(_ => {
      console.log(`app listening on port ${port}`)
    }).catch(err => {
      console.error('err', err)
    })
  }
}

app.use(async (ctx, next) => {
  console.log('middleware 1')
  await next()
  console.log('middleware 1 after')
})

app.use(async (ctx, next) => {
  console.log('middleware 2')
  await next()
  console.log('middleware 2 after')
})

app.use(async (ctx, next) => {
  console.log('async')
  await next()
  await new Promise((resolve) => {
    setTimeout(() => {
      console.log('async end in 3s')
      resolve()
    }, 3000)
  })
})

app.use(async (ctx, next) => {
  console.log('middleware 3')
  await next()
  console.log('middleware 3 after')
})

app.listen(3000)

上述代码执行结果为:

zsh
middleware 1
middleware 2
async
middleware 3
middleware 3 after
async end in 3s
middleware 2 after
middleware 1 after
app listening on port 3000

TIP

关于 koaexpress 的中间件模型,其实大致是一样的。

譬如 koaexpress 都是基于 next 函数来构建中间件执行链,且顺序都是 input1 => next2 => input2 => output2 => outpu1

由于 koa 是基于 promise 构建的中间件执行链,因此它对于异步任务的处理方式,要比 express 更加友好

上述结论,我们可以通过比较 koaexpressdemo 代码执行结果,来理解。