中间件模式被广泛应用在 Koa框架中,它允许在调用目标函数之前,随意插入其他函数预先对数据进行处理、过滤。在这个过程里面你可以打印数据、或者停止往下执行中间件等。数据就像水流一样经过中间件的层层的处理、过滤,最终到达目标函数。请你模拟一个中间件模式,可以达到以下效果:
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 插入任意中间件,中间件是一个函数,可以被传入一个 ctx 和 next;调用 next 的时候会执行下一个中间件。如果不调用 next 会阻止接下来所有的中间件的执行,也不会执行 app.callback。
请你补全 app 的实现,请不要添加额外的全局变量。
答案
我之前用过koa,大概知道中间件是用栈的方式实现的,也大概知道用reduce把所有函数嵌套起来返回应该就可以了,但是实际操作起来却在next函数上踩坑,完全搞不懂怎样把控制权交接给下一个函数。在查过大量资料之后,发现对于next的实现有两个简单可行的方案:
方案一:对next函数进行定义
const app = {
midware:[],
callback(ctx) {
console.log(ctx);
},
use(fn) {
this.midware.push(fn)
},
go(ctx) {
let fnIdx=0
const next=()=>{
fnIdx++
}
this.midware.forEach((fn,idx)=>{
if(fnIdx==idx)
fn(ctx,next)
});
if(fnIdx==this.midware.length)
this.callback(ctx)
}
}
首先定义midware数组,每一次调用use就把中间件push进该数组。在执行go函数时,将重新定义的next方法加入fn中执行。若某一中间件中没有定义next()函数,那么他之后所有中间件的fnIdx将会与idx不同,这就实现了异常退出的功能,而当所有中间件执行完毕后,fnIdx==this.midware.length
等式将成立,此时调用回调函数,完成app加载。非常巧妙,值得借鉴。
方案二:使用reduce遍历
const app = {
midware:[],
callback(ctx) {
console.log(ctx);
},
use(fn) {
this.midware.unshift(fn)
},
go(ctx){
let fn=this.midware.reduce((pre,cur)=>{
return (ctx)=>cur(ctx,()=>pre(ctx))
},this.callback)
fn(ctx)
}
}
这种方法比较常规,首先reduce的初始值为this.callback,每次遍历结束将return的值赋给pre,从而实现函数嵌套的效果,这里注意由于在中间件中next是以函数的形式存在的,所以cur的第二个参数,应该是()=>pre(ctx)
,这样在调用fn的时候才能正确进行迭代。
网友评论