• koa2 中间件里面的next到底是什么


    koa2短小精悍,女人不爱男人爱。

    之前一只有用koa写一点小程序,自认为还吼吼哈,知道有一天某人问我,你说一下 koa或者express中间件的实现原理。然后我就支支吾吾,好久吃饭都不香。

    那么了解next的最好办法是什么, 百度,谷歌,知乎?  没错,肯定有用,我觉得最有用的是看源码和debug去理解。

    先看下面的一段代码 ,会输出什么,只会输出  X-Response-Time

    const Koa = require('koa');
    const app = new Koa();
    
    // x-response-time
    
    app.use(async (ctx) => {
      const start = Date.now();
      //await next();
      const ms = Date.now() - start;
      ctx.set('X-Response-Time', `${ms}ms`);
      console.log('X-Response-Time', `${ms}ms`)
    });
    
    // logger
    
    app.use(async (ctx) => {
      const start = Date.now();
      //await next();
      const ms = Date.now() - start;
      console.log(`${ctx.method} ${ctx.url} - ${ms}`);
    });
    
    // response
    
    app.use(async ctx => {
      console.log('Hello World')
      ctx.body = 'Hello World';
    });
    
    app.listen(3000);

    然后修改成如下代码,会依次输出

    Hello World
    GET / - 8
    X-Response-Time 1040ms
    const Koa = require('koa');
    const app = new Koa();
    
    // x-response-time
    
    app.use(async (ctx, next) => {
      const start = Date.now();
      await next();
      const ms = Date.now() - start;
      ctx.set('X-Response-Time', `${ms}ms`);
      console.log('X-Response-Time', `${ms}ms`)
    });
    
    // logger
    
    app.use(async (ctx, next) => {
      const start = Date.now();
      await next();
      const ms = Date.now() - start;
      console.log(`${ctx.method} ${ctx.url} - ${ms}`);
    });
    
    // response
    
    app.use(async ctx => {
      console.log('Hello World')
      ctx.body = 'Hello World';
    });
    
    app.listen(3000);

    从上面的结果看来,发现什么没有,没有next 就没有下面的执行,可就简单的一个 await next(), 为嘛会有这种效果,这里,我首先简单说一下koa2中间件的实现原理。

    这里先从 koa的使用说起

    const Koa = require('koa');
    const app = new Koa();
    app.use(async (ctx, next) => {
      const start = Date.now();
      await next();
      const ms = Date.now() - start;
      console.log(`${ctx.method} ${ctx.url} - ${ms}`);
    });
    app.listen(3000);

    我们顺藤摸瓜,打开 koa里面的application.js (或者直接debug进入),

    1.首先看 use ,就是push一个函数到 this.middleware

    2. 再看listen, 方法里面 http.createServer(this.callBack), this.callBack返回的是 function(req,res){......}的函数,连起来就是 http.createServer(function(req,res){....}),标准的http创建服务的方法

    3.  最后看callback,里面的核心方法, compose(this.middleware) 返回一个promise,处理完毕后再执行 handleResponse

    这三个连起来,就是每次请求的时候,先进入callback, compose中间件,执行完毕后,接着处理请求。那剩下的重点变为 compose 

      use(fn) {
        if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
        if (isGeneratorFunction(fn)) {
          deprecate('Support for generators will be removed in v3. ' +
                    'See the documentation for examples of how to convert old middleware ' +
                    'https://github.com/koajs/koa/blob/master/docs/migration.md');
          fn = convert(fn);
        }
        debug('use %s', fn._name || fn.name || '-');
        this.middleware.push(fn);
        return this;
      }
      listen(...args) {
        debug('listen');
        const server = http.createServer(this.callback());
        return server.listen(...args);
      }
      callback() {
        const fn = compose(this.middleware);
    
        if (!this.listeners('error').length) this.on('error', this.onerror);
    
        const handleRequest = (req, res) => {
          res.statusCode = 404;
          const ctx = this.createContext(req, res);
          const onerror = err => ctx.onerror(err);
          const handleResponse = () => respond(ctx);
          onFinished(res, onerror);
          return fn(ctx).then(handleResponse).catch(onerror);
        };
    
        return handleRequest;
      }

    我们继续深入研究 compose看源码,核心依旧是标粗的部分,核心的核心就是dispatch, dispatch会根据 middleware 的长度,依次执行。

    'use strict'
    
    /**
     * Expose compositor.
     */
    
    module.exports = compose
    
    /**
     * Compose `middleware` returning
     * a fully valid middleware comprised
     * of all those which are passed.
     *
     * @param {Array} middleware
     * @return {Function}
     * @api public
     */
    
    function compose (middleware) {
      if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
      for (const fn of middleware) {
        if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
      }
    
      /**
       * @param {Object} context
       * @return {Promise}
       * @api public
       */
    
      return function (context, next) {
        // last called middleware #
        let index = -1
        return dispatch(0)
        function dispatch (i) {
          if (i <= index) return Promise.reject(new Error('next() called multiple times'))
          index = i
          let fn = middleware[i]
          if (i === middleware.length) fn = next
          if (!fn) return Promise.resolve()
          try {
            return Promise.resolve(fn(context, function next () {
              return dispatch(i + 1)
            }))
          } catch (err) {
            return Promise.reject(err)
          }
        }
      }
    }

      注意下面,如果 next为空,直接返回,也就出现了我们第一段代码的情况,后面的中间件就game over了。

        if (i === middleware.length) fn = next
          if (!fn) return Promise.resolve()

    在往下分析,假定现在执行第一个fn,这个时候第一个fn是什么

            return Promise.resolve(fn(context, function next () {
              return dispatch(i + 1)
            }))

    这时候fn为如下, 

    fn = async (ctx, next) => {
      const start = Date.now();
      await next();
      const ms = Date.now() - start;
      ctx.set('X-Response-Time', `${ms}ms`);
      console.log('X-Response-Time', `${ms}ms`)
    }

     与上面的参数对应关系如下

    context :ctx,

    next : function next(){ return dispatch(i+1)}

    所以 await next() 就等于 await function next(){ return dispatch(i+1)} , 而 dispatch(i+1)就进入了下一个中间件了。

    核心就是 dispatch(i+1),也就是dispatch(1) , dispatch本身返回promise, 所以你就在这里 await 。

    依此类推 disptach(1) 会执行 this.middleware[1],  那个时候 fn就为 logger执行的函数,就这么推下去。

    关于结束,还是 next 不存在的时候。 结果完毕后,再依次往上走。

    所以执行的顺序是越先注册越后执行, 当然还得看你 await next() 放在什么位置。 因为这里我的 console.log都放在了 await的后面,都放到前面,结果如何,亲自测试一下喽。 

    最后简单的模拟一下 Promise.resolve(fn()), 

    1.  fn为一个异步函数,所以里面可以await

    2.  fn最后返回的是一个Promise对象

    3. 当Promise.then, Promise.resolve返回是一个Promise对象时,会执行该Promise对象,并进入下一个环节

    4 . 所以p1, p2依次执行,最后结果为6

    var p1 = function () {
        return new Promise((resolve, reject) => {
            setTimeout(function () {
                console.log('p1', new Date().toLocaleString())
                resolve(1)
            }, 2000)
        })
    }
    
    var p2 = function () {
        return new Promise((resolve, reject) => {
            setTimeout(function () {
                console.log('p2', new Date().toLocaleString())
                resolve(6)
            }, 4000)
        })
    }
    
    console.log('start', new Date().toLocaleString())
    Promise.resolve(fn()).then(r => {
        console.log('end', new Date().toLocaleString())
        console.log(r)
    })
    
    async function fn() {
        let a = await p1()
        let b = 4
        return p2()
    }
    
    // start 2018/3/15 下午8:16:37   
    // p1 2018/3/15 下午8:16:39  
    // p2 2018/3/15 下午8:16:43  
    // end 2018/3/15 下午8:16:43  
    // 6
    

      

  • 相关阅读:
    03JavaScript程序设计修炼之道 2019-06-23_14-32-17
    03JavaScript程序设计修炼之道 2019-06-20_21-30-17
    03JavaScript程序设计修炼之道 2019-06-20_21-12-16
    03JavaScript程序设计修炼之道 2019-06-20_20-51-20
    03JavaScript程序设计修炼之道-2019-06-20_20-31-49
    03JavaScript程序设计修炼之道-2019-06-20_20-07-53
    02-CSS基础与进阶-day15
    02-CSS基础与进阶-day14
    02-CSS基础与进阶-day13_2018-09-21-21-13-20
    python基础(6)集合
  • 原文地址:https://www.cnblogs.com/cloud-/p/7239819.html
Copyright © 2020-2023  润新知