ScriptQJ闯关之中间件

515 阅读3分钟
原文链接: zhuanlan.zhihu.com

上篇闯关了组合函数编程compose,这篇来到中间件一关。

先上题目:

中间件模式(middleware)是一种很常见、也很强大的模式,被广泛应用在 Express、Koa、Redux 等类库和框架当中。如果你能在自己的代码中也使用灵活这种模式能给你的程序带来更大的便利性和灵活性。
简单来说,中间件就是在调用目标函数之前,你可以随意插入其他函数预先对数据进行处理、过滤,在这个过程里面你可以打印数据、或者停止往下执行中间件等。数据就像水流一样经过中间件的层层的处理、过滤,最终到达目标函数。请你模拟一个中间件模式,可以达到以下效果:
const app = {
  callback (ctx) {
    console.log(ctx)
  },
  
  use (fn) {
    /* TODO */
  },
  
  go (ctx) {
    /* TODO */
  }
}

app.use((ctx, next) => {
  ctx.name = 'Lucy'
  next()
})

app.use((ctx, next) => {
  ctx.age = 12
  next()
})

app.use((ctx, next) => {
  console.log(`${ctx.name} is ${ctx.age} years old.`) // => Lucy is 12 years old.
  next()
})

// ... 任意调用 use 插入中间件

app.go({}) // => 启动执行,最后会调用 callback 打印 => { name: 'Lucy', age: 12  }
ctx 参数就是 app.go 接受的对象。调用 app.go 其实会调用目标函数 app.callback,但是调用 app.callback 之前我们可以先让参数 ctx 通过一系列的中间件,最后才会传递给 app.callback
使用 app.use 插入任意中间件,中间件是一个函数,可以被传入一个 ctxnext;调用 next 的时候会执行下一个中间件。如果不调用 next 会阻止接下来所有的中间件的执行,也不会执行 app.callback
请你补全 app 的实现,请不要添加额外的全局变量。
const app = {
  callback (ctx) {
    console.log(ctx)
  },
  
  use (fn) {
    /* TODO */
  },
  
  go (ctx) {
    /* TODO */
  }
}

涉及到中间件则绕不开TJ大神的Koa,点此了解下Koa1和Koa2的中间件

代码给出了需要实现的中间件大致架子,这样感觉实现起来容易了许多,基于这个需求

//启动执行,最后会调用 callback 打印 => { name: 'Lucy', age: 12 }

新开一个fns属性,每次调用fn我们可以先把use中的function存到fns中,在调用use时遍历app.fns进行执行每个use的fn,用idx记录fn执行的数量,每次调用next时idx++, 如果所有的fn都执行完那么调用callback

首先实现一版:

const app = {
  fns: [],
  callback (ctx) {
    console.log(ctx)
  },
  use (fn) {
    this.fns.push(fn)
  },
  go (ctx) {
    let nexts = []
    let idx = 0
    this.fns.forEach((fn, index) => {
      nexts[index] = () => { idx++ }
      fn(ctx, nexts[index])
    })
    if(this.fns.length === idx) this.callback(ctx) //fn都执行完那么调用callback
  }
}

开开心心的提交,结果:

继续修改:

const app = {
  fns: [],
  callback (ctx) {
    console.log(ctx)
  },
  use (fn) {
    this.fns.push(fn)
  },
  go (ctx) {
    let nexts = []
    let idx = 0
    this.fns.forEach((fn, index) => {
      nexts[index] = () => { idx++ }
      if (idx === index) fn(ctx, nexts[index]) // idx === index保证next被调用了再继续往下执行
    })
    if(this.fns.length === idx) this.callback(ctx) //fn都执行完那么调用callback
  }
}

提交:

然而看了讨论区@ackerman 的实现,next作用是idx++,可以抽出来:

const app = {
  fns: [],
  callback (ctx) {
    console.log(ctx)
  },
  use (fn) {
    this.fns.push(fn)
  },
  go (ctx) {
    let idx = 0
    const next = () => { idx++ }
    this.fns.forEach((fn, index) => {
      if (idx === index) fn(ctx, next)// idx === index保证next被调用了再继续往下执行
    })
    if(this.fns.length === idx) this.callback(ctx)//fn都执行完那么调用callback
  }
}
app.use((ctx, next) => {
  ctx.name = 'Lucy'
  next()
})

然而总感觉看起来还有需要改进的地方,让我们看大神@CODEHZ的实现:

const app = {
  callback (ctx) {
    console.log(ctx)
  },
  
  use (fn) {
    app.middleware = app.middleware || []
    app.middleware.push(fn)
  },
  
  go (ctx) {
    app.middleware = app.middleware || []
    app.middleware.reduceRight((p, c) => () => c(ctx, p), () => app.callback(ctx))()
  }
}

只能感叹灵活使用reduce真的可以为所欲为

最后,欢迎点此进入

欢迎点此进入叶雨森主页