likes
comments
collection
share

koa 洋葱模型调用机制及实现

作者站长头像
站长
· 阅读数 57

提起 koa,很多同学第一句话肯定就是“洋葱模型”。

这个调用模型非常地形象,对它理解了之后,也会觉得它的机制非常地精妙。

洋葱模型

我们来看这样的一个例子:

const Koa = require('koa')

const app = new Koa()

app.use(async (ctx, next) => {
   console.log('a-1')
   await new Promise((resolve, reject) => {
       setTimeout(() => {
          console.log('---after 2000 ms---')
          console.log('a-2')
          resolve()
       }, 2000)
   })
   const nextResult = await next()
   console.log(nextResult)
})

app.use(async (ctx, next) => {
   console.log('b-1')
   await new Promise((resolve, reject) => {
      setTimeout(() => {
         console.log('---after 2000 ms---')
         console.log('b-2')
         resolve()
      }, 2000)
   })
   const nextResult = await next()
   console.log(nextResult)
   return 'b-3'
})

app.use(async (ctx, next) => {
   console.log('c-1')
   await new Promise((resolve, reject) => {
      setTimeout(() => {
         console.log('---after 2000 ms---')
         console.log('c-2')
         resolve()
      }, 2000)
   })
   return 'c-3'
})

app.listen(10000, () => {
   console.log('listening...')
})

启动应用,访问 **http://localhost:10000**, 它在控制台打印的结果如下:

a-1
---after 2000 ms---
a-2
b-1
---after 2000 ms---
b-2
c-1
---after 2000 ms---
c-2
c-3
b-3

可以看到,类似于 express.js 的 next,每次调用的时候,就会开始执行下一个中间件。随着 next 的调用而逐级进入。

然后当最深层的中间件 return 或执行完毕时,就会跳到上一层的 await next() 处,并将返回值带入。像这样逐层返回。

这样调用方式被描述为 洋葱模型

koa 洋葱模型调用机制及实现

洋葱模型的实现

koa 是一个非常精简的库,它其实就做了两件简单的事情:

一件是创建了两个对象:requestresponse, 通过 gettersetter 简化了对原生的 reqres 的读写。所以我们在 koa 中应该操作的是它的 requestresponse 这两个对象。并把他们都挂载到 context 对象上,也就是中间件的第一个参数。

另一个就是实现了洋葱模型的调用逻辑。在这里,我们简化一下它的实现:

首先搭建基本的骨架,包括:

  • 一个属性 middlewares,用来存放中间件
  • use 方法,用来新增中间件
  • listen 方法,开启监听
  • _handleRequest 方法,请求到来时,创建一个 Context,然后调用 _compose 方法来执行中间件。

我们忽略 Context 的具体实现,来看代码:

const http = require('http');
const Context = require('./context');

module.exports = class Koa {
   constructor() {
      this.middlewares = []
   }

   use(middleware) {
      this.middlewares.push(middleware)
   }
   
   _compose(context) {
      // ....
   }

   _handleRequest(req, res) {
      const ctx = new Context(req, res)

      this._compose(ctx).then(() => {
         console.log('end')
      }).catch((err) => {
         console.log('err')
      })
   }

   listen(...args) {
      const server = http.createServer((req, res) => {
          this._handleRequest(req, res)
      })
      server.listen(...args)

   }
}

然后我们具体实现 _compose 方法:

compose(context) {
   const dispatch = (index) => {
      if (index === this.middlewares.length) {
         return Promise.resolve()
      }

      const middleware = this.middlewares[index]
      const next = () => dispatch(index + 1)

      try {
         return Promise.resolve(middleware(context, next))
      } catch(err) {
         return Promise.reject(err)
      }
   }

   return dispatch(0)
}

好,带入文章开头的例子中进行执行,可以看到和原生的 koa 的结果一致。

现在我们就已经实现了洋葱模型了。当然 koa 源码中还有部分其他的情况的处理,但是大体逻辑就是这样。

是不是非常精妙?

后记

前几天我写了一篇 express 结构剖析,其中细致地分析了 express 复杂的结构。而 koa 却比它内容很多,它本身就是这样一个精简的架子,很多功能并没有像 express 内置进去了,而是调用额外的中间件库去实现这个功能。

express 和 koa 是现在很多功能更全的框架的底层框架,厘清他们的实现原理,有助于我们更好地学习和使用更大而全的框架,如 Nest.jsEGG.js

转载自:https://juejin.cn/post/7128777268339408927
评论
请登录