JavaScript 进阶营

js中不同框架中间件实现的区别

2018-07-09  本文已影响1人  勤奋的大鱼

  用过express,koa,react(redux)的都会知道中间件,那么在这三个框架的中间件的实现有什么不同呢?

Express中间件

首先来看看express,分析express的源码可以看出express中router中间件和express是强耦合的,express把中间件的实现放在了router中去。
大概讲一下这里的中间件实现逻辑:

  1. 通过app.use收集中间件到stacks中去
  2. 有客户端请求时,遍历执行所有的中间件(把下一个中间件作为上一个中间件的next传入)

以下是这个逻辑的简单实现:

    var app = {
      stacks: [],
      // 收集中间件
      use: function () {
        var fns = arguments
        for (var i = 0, l = fns.length; i < l; i++) {
          this.stacks.push(fns[i])
        }
      },
      // 触发中间件
      handle: function (req, res) {
        var idx = 0,
            stacks = this.stacks;
        next()
        function next () {
          if (idx === stacks.length) {
            return;
          }
          var match = stacks[idx];
          idx++;
          match(req, res, next)
        }
      }
    }
    app.use(function (req, res, next) {
      console.log(0)
      next()
      console.log('00')
    }, function (req, res, next) {
      console.log(1)
      next()
      console.log(11)
    }, function () {
      console.log(2)
    })
    app.handle()
Koa中间件
    function compose (middleware) {
      return function (context, next) {
        // last called middleware #
        let index = -1
        return dispatch(0)
        function dispatch (i) {
          // 注释的部分为Koa源码,源码对async/await更加友好,为了简便对比,这里省略一些
          // if (i <= index) return Promise.reject(new Error('next() called multiple times'))
          index = i
          let fn = middleware[i]
          if (i === middleware.length) return Promise.resolve()
          // if (!fn) return Promise.resolve()
          // try {
          //   return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
          // } catch (err) {
          //   return Promise.reject(err)
          // }
          return fn(context, dispatch.bind(null, i + 1))
        }
      }
    }
    var app = {
      stacks: [],
      use: function () {
        var fns = arguments
        for (var i = 0, l = fns.length; i < l; i++) {
          this.stacks.push(fns[i])
        }
      },
      listen: function (...args) {
        // const server = http.createServer(this.callback())
        // return server.listen(...args)
        // 以下代码是为了方便调用中间件
        var cb = this.callback()
        cb()
      },
      callback: function () {
        var fn = compose(this.stacks)
        return fn
      }
    }
    app.use(async function(ctx, next) {
      console.log(1)
      await next()
      console.log(11)
    }, async function (ctx, next) {
      console.log(2)
      await next()
      console.log(22)
    }, async function (ctx, next) {
      console.log(3)
      await next()
      console.log(33)
    })
    app.listen()

中间件在Koa中的实现其实和Express没有本质的区别,逻辑基本上是一样的,多了一步compose,就是把中间件组合成一个函数。
说起Koa的中间件,很多地方都会提到洋葱模型,关于洋葱模型,按之前express的例子来说,就是按输出0,1,2,11,00的顺序执行,所以说洋葱模型并不是Koa特有的东西,另外,async/await也不是Koa才有的,async/await只是一个语法糖而已。
在我看来,Koa的中间件和Express的中间件区别在于Koa对异步中间件的处理可能更友好一些,从代码中的注释的源码部分可以看出。(对Koa没有实际的项目经验,仅就自己对源码的一些理解,有不对的地方希望可以不吝赐教)

Redux中间件

Redux中间件和这里使用的场景不同,这里我按Redux中间件的写法实现在node框架中的中间件。

    function compose (middleware) {
      return middleware.reduceRight((composed, f) => f(composed), () => {})
    }
    var app = {
      stacks: [],
      use: function () {
        var fns = arguments
        for (var i = 0, l = fns.length; i < l; i++) {
          this.stacks.push(fns[i])
        }
      },
      listen: function (...args) {
        // const server = http.createServer(this.callback())
        // return server.listen(...args)
        var cb = this.callback()
        cb()
      },
      callback: function () {
        var ctx = {request: {}, response: {}}
        var middlewares = this.stacks.map(middleware => middleware(ctx))
        var fn = compose(middlewares)
        return fn
      }
    }
    app.use(ctx => next => () => {
      console.log(1, ctx)
      next()
      console.log(11)
    }, ctx => next => () => {
      console.log(2, ctx)
      next()
      console.log(22)
    }, ctx => next => () => {
      console.log(3, ctx)
      next()
      console.log(33)
    })
    app.listen()

比较有意思的是compose函数的写法,利用es6,仅有一行代码就实现了。
这里是函数式编程中的currying,是一种使用匿名单参数函数实现多参数函数的方法。

上一篇下一篇

猜你喜欢

热点阅读