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的后面,都放到前面,结果如何,亲自测试一下喽。

时间: 2024-12-05 11:29:57

koa2 中间件里面的next到底是什么的相关文章

koa2 use里面的next到底是什么

koa2短小精悍,女人不爱男人爱. 之前一只有用koa写一点小程序,自认为还吼吼哈,知道有一天某人问我,你说一下 koa或者express中间件的实现原理.然后我就支支吾吾,好久吃饭都不香. 那么了解next的最好办法是什么, 百度,谷歌,知乎?  没错,肯定有用,我觉得最有用的是看源码和debug去理解. 先看下面的一段代码 ,会输出什么,只会输出  X-Response-Time const Koa = require('koa'); const app = new Koa(); // x-

自定义Button,复写里面的onKeyDown,不起作用

李刚的Android疯狂讲义真是"疯狂",浪费了3天时间,到底是他的代码有问题,还是怎么的不得而知. 问题描述:他的书里面第3.3基于回调事件处理Propagation的例程.是为了掩饰基于回调事件传播的例程序,源代码如下: MyButton.java public class MyButton extends Button { public MyButton(Context context , AttributeSet set) { super(context , set); } @

VC代码生成里面的/MT /MTd /MD /MDd的意思

VC代码生成里面的/MT /MTd /MD /MDd的意思. 意思上已经很明白了.但是往往很多人弄不清楚到底怎么选择. /MT是 "multithread, static version " 意思是多线程静态的版本. 定义了它后,编译器把LIBCMT.lib 安置到OBJ文件中,让链接器使用LIBCMT.lib 处理外部符号. /MD是 "multithread- and DLL-specific version" ,意思是多线程DLL版本,定义了它后,编译器把MS

前端里移动端到底比pc端多哪些知识?

前端里移动端到底比pc端多哪些知识,为啥面试时好多公司都问h5水平如何? 我做过几年的web前端开发,就简单谈谈自己的感受吧. 首先来看看PC端和移动端在前端开发上的一些区别: (1)PC考虑的是浏览器兼容性,移动端开发考虑的更多的是手机兼容性,因为目前不管是android手机还是ios手机,一般浏览器用的都是webkit内核,所以做移动端开发,更多考虑的应该是手机分辨率的适配,和不同操作系统的略微差异化: (2)在部分事件的处理上,移动端自然是偏向于触屏的,所以触屏事件的一些规律要多摸索一下,

Afianl框架里面的FinalBitmap加载网络图片

在Afianl框架里,FinalBitmap如何加载网络图片?有什么简单的方法吗?现在让麦子学院的android开发老师讲讲afina框架里面的finalbitmap加载网络图片的方法,Afinal里边FinalBitmap:用于显现bitmap图像,而无需思考线程并发和oom等疑问. 1.测验恳求 运用页面翻开http://avatar.csdn.net/C/6/8/1_bz419927089.jpg"可以看到一张图像. 2.新建FinalBitmap目标 1 FinalBitmap bitm

函数的上下文就是函数里面的this是谁

规律1:函数用圆括号调用,函数的上下文是window对象 比如小题目: function fun(){ var a = 888; alert(this.a); //实际上访问的是window.a } var a = 666; fun(); //弹出666 函数function fun(){}的上下文是什么!不要看它怎么定义,要看它怎么调用!!此时是fun()函数名加上圆括号直接调用,此时上下文就是window对象! 而我们知道:所有的全局变量都是window对象的属性,(注意:函数里面的局部变量

如何去除项目里面的SVN和CVS文件夹

SVN和CVS是我们常使用的项目版本管理工具,为我们的工作带来了很大的方便.但是,有时候我们需要删除里面的SVN和CVS文件夹. 1 如何快速的删除项目中的版本控制文件夹  (1) 删除项目中的SVN文件夹.我们可以使用bat脚本来实现快速递归删除,代码如下: @echo off echo Deleting SVN folders and files under: %1 REM Open Folder specified by parameter. cd %1 REM Recursive del

提取数据库字段里面的值,并改变+图片懒加载,jquery延迟加载

要求:手机端打开某个页面的详细信息,因为网速或者别的原因,响应太慢,因为图片大的原因,希望先进来,图片在网页运行的情况再慢慢加载(jquer延迟加载) http://www.w3cways.com/1765.html()  困难:因为页面的图片是用编辑器加进去的        :图片与文字存到数据库的字段中 :实现思路:从数据库中读取这个字段,把字段里面的src替换成lazyload 然后再返回给页面 这里主要讲是怎么把从数据库取到的数据里面的字段里面的值替换然后与页面直接的数据转换 首先 pu

直接修改别人jar包里面的class文件 工具:jclasslib

出于某些原因 需要把别人jar包里面的class修改一下信息 配置文件*.properties MANIFEST.MF 这些东西可以直接用记事本打开修改 然后替换掉就OK.. 在网上游荡了半天,没有找到合适的方法 开始我是先用jd-gui反编译 把我需要修改的那个A.class文件反编译出来把代码保存到A.Java 然后直接在dos里面用javac A.java编译 问题来了.. 因为A.java引用了很多其他jar下面的方法 而且A.java有package 鼓捣了半天以后 最终结果:编译无法