zoukankan      html  css  js  c++  java
  • 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
    

      

  • 相关阅读:
    matlab矩阵中如何去掉重复的行;如何找到相同的行,并找到其位置
    Coursera 机器学习 第9章(下) Recommender Systems 学习笔记
    机器学习基石笔记1——在何时可以使用机器学习(1)
    Coursera 机器学习 第9章(上) Anomaly Detection 学习笔记
    matlab安装过程的被要求的配置程序
    jdk环境变量配置
    Coursera 机器学习 第8章(下) Dimensionality Reduction 学习笔记
    Coursera 机器学习 第8章(上) Unsupervised Learning 学习笔记
    Coursera 机器学习 第7章 Support Vector Machines 学习笔记
    linux服务器---squid限制
  • 原文地址:https://www.cnblogs.com/cloud-/p/7239819.html
Copyright © 2011-2022 走看看