中间件模式

作者: 千茉紫依 | 来源:发表于2019-04-01 20:08 被阅读0次

    中间件模式被广泛应用在 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的时候才能正确进行迭代。

    相关文章

      网友评论

        本文标题:中间件模式

        本文链接:https://www.haomeiwen.com/subject/bgebbqtx.html