经典的js代码——koa-compose

总有一些js代码写出来让人啧啧称奇,就比如koa-compose的源码:
koa-compose就是用来处理koa的中间件的,其主要代码就是如下函数:

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) {
    /* 这两句话的意思是来防止我们执行两次`next()`,试想一下:当我们第一次执行`next()`的时候,`index=i`然后第二次在执行,由于两次的dispatch.bind(null, i + 1)是相同的,所以执行第二次`next()`的时候`index==i`于是if条件就会成立,然后报错。 */
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i] //让fn指向
      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)
      }
    }
  }
}

仔细研究这个函数就可以发现:

  1. 首先对于函数行参进行类型判断,类型必须是一个函数数组,否则报错
  2. 返回一个闭包(仔细观察可以发现返回的匿名函数使用了变量middleware),这使得compose函数的作用域一直存在(暂时不知道有什么意义???
    为什么不把middleware作为一个参数写到这个匿名function的行参呢?大概是为了防止外部修改middleware?
    可是这样一想的话为什么不用bind来绑定这个参数为middleware呢?突然又发现简单bind的话,会影响原先的context和next参数)

正式看看这个函数并,结合以下代码来测试:

function a(ctx,next){
  console.log("111")
  next()
  // next()
}
function b(ctx,next){
  console.log("222")
  next()
}
function c(ctx,next){
  console.log("333")
  next()
}
compose([a,b,c])().then(()=>{
  console.log("over")
})

当我们compose([a,b,c])的时候返回了一个函数,然后再()调用这个函数,在这个函数中就会执行dispatch(0)
我么来仔细看看dispatch函数:

 function dispatch (i) {
 /* 这两句话的意思是来防止我们执行两次`next()`,试想一下:当我们第一次执行`next()`的时候,`index=i`然后第二次在执行,由于两次的dispatch.bind(null, i + 1)是相同的,所以执行第二次`next()`的时候`index==i`于是if条件就会成立,然后报错。 */
   if (i <= index) return Promise.reject(new Error('next() called multiple times'))
   index = i
   let fn = middleware[i] //让fn指向当前该执行的函数,也就是我们写的中间件
   if (i === middleware.length) fn = next //当middleware中的函数执行完毕后(也就是所有的中间件都执行完了)去执行next这个行参传进来的函数
   if (!fn) return Promise.resolve()// 如果next参数为空,且当middleware执行完毕后返回一个resolve状态的promise,否则等到执行完next传进来的函数再返回一个resolve状态的promise
   try {
   // 返回一个promise,并且执行fn函数,将context参数传入fn作为fn的ctx,将dispatch.bind(null, i + 1)传入fn作为fn的next
   //这里将一个绑定了参数i的dispatch传入中间件,所以next并不是我们要执行的下一个中间件,而是next函数中可以访问到middleware也就是所有的中间件,只是因为i被bind绑定了,所以感觉像是下一个中间件函数
     return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
   } catch (err) {
  // 用来捕获中间件执行过程中的错误
     return Promise.reject(err)
   }
   //这里通过Promise.resolve来执行中间件只要是为了支持async/await,不然你这样写代码岂不是就报错了吗 await next()
 }

最后你可能要问洋葱圈模型在哪里?自己仔细想想吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值