重学 koa

264 阅读1分钟

koa 实现可扩展各功能主要是它的中间件设计,本文是再读koa源码关于中间件设计的一些心得。

koa-compose源码

/**
 * Compose `middleware` returning
 * a fully valid middleware comprised
 * of all those which are passed.
 *
 * @param {Array} middleware
 * @return {Function}
 * @api public
 */

function compose (middleware) {
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  for (const fn of middleware) {
    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
  }

  /**
   * @param {Object} context
   * @return {Promise}
   * @api public
   */

  return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      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)
      }
    }
  }
}

next方法 是什么? 简单的来说 next 就是包装过的下一个 middleware

心得

为了实现洋葱模型,我尝试了几种实现compose的方式(主要是用一个 stack 数据结构存储 Promise resolve 数据),但都没有完美实现这个特性,看了源码后豁然开朗。

那废话不多说,上demo。

    async function middlwareA(_, next) {
        // Block A Prev
        await next()
        // BLock A After
    }
    
    async function middlwareB(_, next) {
        // Block B Prev
        await next()
        // BLock B After
    }
    
    async function middlwareC(_, next) {
        // Block C Prev
        await next()
        // BLock C After
    }
    
    koa.use(midddlewareA)
    koa.use(midddlewareB)
    koa.use(midddlewareC)

在屏蔽掉 js async await coroutine 的细节情况下,下面画出执行以及调用细节, 当一个 request 到达 node handleRequest 后:

由此便可知,这种设计主要是基于 javascript call stack,而这种实现方式有一个显然易见的弊端,也就是当中间件数量过多时, 会发生调用栈溢出 Maximum call stack size exceeded

结语

希望可以帮助到各位,如有任何问题,欢迎联系 Email:

fireflyEngineer@gmail.com