#52 中间件模式


  • 0
    administrators

    中间件模式(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 的实现,请不要添加额外的全局变量。


  • 0
    管理员

    不知道是哪个测试用例报了这个错误:Cannot read property 'next' of null

    const app = {
        //已注册的函数的指针链
        _fn : null,
    
        callback (ctx) {
          console.log(ctx)
        },
    
        // 注册中间件
        // 中间件的功能是把注册的函数添加到指针链末尾
        use(fn) {
            //第一次注册
            if (!this._fn) {
                this._fn = fn;
                return;
            }
            let current = this._fn;
            while (current.next) {
                current = current.next;
            }
            //在指针链末尾添加注册的函数
            current.next = fn;
        },
    
        // 触发事件
        go(obj) {
            // 调用 middleware
            const fn = this._fn;
            let current = this._fn;
            while (current.next) {
                current = current.next;
            }
            current.next = this.callback
            if (fn.next) {
                const nextFn = (m) => {
                    return () => {
                        if (m.next) {
                            m.next(obj, nextFn(m.next));
                        }
                    };
                }
                //神递归
                fn(obj, nextFn(fn));           
            }
        }
    }
    

  • 0
    administrators

    @陈小俊 考虑没有中间件的情况


  • 0
    管理员

    @胡子大哈 中间件没有被正确调用,已经哭晕


  • 0
    administrators

    @陈小俊 你多插入几个中间件试试看?


  • 0
    administrators

    @陈小俊

    app.use((ctx, next) => {
      ctx.a = 1
      next()
    })
    
    app.use((ctx, next) => {
      ctx.b = 2
      next()
    })
    
    app.use((ctx, next) => {
      ctx.c = 3
      next()
    })
    
    最后应该打印 {a: 1, b: 2, c: 3}
    

  • 0
    管理员

    @胡子大哈

    0_1495790704247_屏幕快照 2017-05-26 下午5.24.28.png
    好像没问题。。


  • 0
    administrators

    @陈小俊

    app.use((ctx, next) => {
      ctx.a = 1
      next()
    })
    
    app.use((ctx, next) => {
      ctx.b = 2
      next()
    })
    
    app.go({}) // => {a: 1, b: 2}
    
    app.use((ctx, next) => {
      ctx.c = 3
      next()
    })
    
    app.go({}) // => {a: 1, b: 2, c: 3}
    

    试试这个呢?


  • 0
    管理员

    改好之后想整理一下,然后发现我已经不认识这个代码了。

    const app = {
        //已注册的函数的指针链
        _fn: null,
    
        callback (ctx) {
          console.log(ctx)
        },
    
        // 注册中间件
        // 中间件的功能是把注册的函数添加到指针链末尾
        use(fn) {
            // 第一次注册
            if (!this._fn) {
                this._fn = fn;
                return;
            }
            let current = this._fn;
            while (current.next) {
                current = current.next;
            }
            //在指针链末尾添加注册的函数
            current.next = fn;
        },
    
        go(obj) {
            if(this._fn == null) {
              this.callback(obj)
              return
            }
            const nextFn = (m) => {
                return () => {
                    if (m.next) {
                        m.next(obj, nextFn(m.next));
                    }
                    else {
                      this.callback(obj)
                    }
                };
            }
            //神递归
            this._fn(obj, nextFn(this._fn));           
        }
    }
    

    我是谁,从哪来 要到哪去

    @ackerMan 还是这位老铁的思想好

    const app = {
      fns: [],
      callback (ctx) {
        console.log(ctx)
      },
      use (fn) {
        this.fns.push(fn)
      },
      go (ctx) {
        let index = 0;
        const next = () => {
          index ++;
        }
        this.fns.map((fn, i) => {
        	if(index == i) fn(ctx, next)
        });
        index === this.fns.length && this.callback(ctx);
      }
    }
    

  • 0
    administrators

    @陈小俊 可以想想有没有别的招数。更简单的方法。


  • 0

    @胡子大哈

    请问,啥叫 第二次 app.go 的时候 app.callback 没有被正确调用


  • 0
    administrators

    @Sunjourney 测试用里是不会 await 你的 go 的,所以你的 callback 会在测试用例之后调用,所以不会监测到。

    这里用同步就好了,题目没有要求异步。


  • 0

    @胡子大哈 3Q,我改改


  • 0

    @胡子大哈 咋感觉通过的代码只能支持同步呢,真实的实现也是这样子的嘛?


  • 0
    administrators

    @Sunjourney 有同步有异步,Redux 这类是应该是同步的。Koa 异步的,异步的可以搞个 (二)。


  • 2

    写的好复杂的样子。。明明一个reduce就能解决问题的

    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.reverse().reduce((p, c) => () => c(ctx, p), () => app.callback(ctx))()
      }
    }
    

  • 0
    administrators

    @CodeHz 你改成 reduceRight 连 reverse 都省了


  • 0

    @胡子大哈 我习惯reverse()+reduce()。因为这样看起来比较统一(都是小写字母)。。


  • 0

    const app = {
      callback (ctx) {
        console.log(ctx)
      },
    
      use (fn) {
        app.middlewares = (app.middlewares || []).concat(fn)
      },
    
      go (ctx) {
        let index = 0
        this.use(this.callback)
    
        const next = () => {
          if (index < app.middlewares.length) {
            app.middlewares[index++](ctx, next)
          }
        }
    
        next()
      }
    }
    

    -> 数据经过中间件以后并不正确,中间件也许没有被正确调用

    请问,这样的写法报错的原因是什么?


  • 0
    administrators

    @Sunjourney 穿插着 go 和 use 来使用看看,应该是这个问题。


登录后回复
 

与 ScriptOJ 的连接断开,我们正在尝试重连,请耐心等待