对koa2源码的分析

最近在学习koa2,但是自己陷入了瓶颈期。就是不知道学什么好,对未来有点迷茫。还好最近看到了知乎上的狼叔的文章

感到迷茫的话就一天阅读十个npm模块。

这让我坚定了阅读源码的信念

KOA2的基本组成


  1. application.js:框架入口;负责管理中间件,以及处理请求
  2. context.js:context对象的原型,代理request与response对象上的方法和属性
  3. request.js:request对象的原型,提供请求相关的方法和属性
  4. response.js:response对象的原型,提供响应相关的方法和属性

我们主要来看看application.js。
那我们就根据自己写koa的习惯来一步步看吧!
我们一般都会先new一个Koa实例

const app = new Koa();

这样我们就先来看看它的构造函数:

// 我们就讲讲一些简单的吧!
constructor() {
    super();

    this.proxy = false;
    this.middleware = []; //中间件栈
    this.subdomainOffset = 2;
    this.env = process.env.NODE_ENV || 'development';
    this.context = Object.create(context); //Object.create(context)创建一个对象,这个对象的原型指向context
    this.request = Object.create(request);
    this.response = Object.create(response);
    // 所以上面三句话就是创造了三个对象
  }

总而言之,它的构造函数只是初始化了一些我们接下来所必须的东西。

接着我们要开始写代码了,比如

app.use(async (ctx, next) => {
  console.log(`1 start`);
  await next();
  console.log('1 end');
})

app.use(async (ctx, next) => {
  console.log(`2 start`);
  await next();
  console.log('2 end');
})

app.use(async (ctx, next) => {
  console.log(`3 start`);
  await next();
  console.log('3 end');
})

这里我们用到了use这个函数 那我们再来看看use吧

use(fn) {
    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
    if (isGeneratorFunction(fn)) {
      deprecate('Support for generators will been removed in v3. ' +
                'See the documentation for examples of how to convert old middleware ' +
                'https://github.com/koajs/koa/tree/v2.x#old-signature-middleware-v1x');
      fn = convert(fn);
    }
    debug('use %s', fn._name || fn.name || '-');
    this.middleware.push(fn);
    return this;
  }

可以看到use函数其实也很简单。首先进行判断,如果传入的中间件不是函数则报错,然后判断如果是generator函数则将其转化为async await类函数。具体怎么转换的我们就先不讲啦。然后再进行统一的错误管理。最后,再把这个中间件推入中间件栈。并返回app实例,以便于我们进行链式操作。

之后我们会怎么写代码呢? 当然是向下面这样

app.listen(3000);

所有准备就绪,就可以开始监听端口啦。

然后这个listen函数 则是最重要的一点!


listen() {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen.apply(server, arguments);
  }

进行统一的错误管理,并用原生的node.js方法创建一个http服务器。而这个服务器的回调函数则是由callback()创建,那么我们很容易想到,callback应该会返回一个方法。让我们来继续看看callback()

callback() {
    const fn = compose(this.middleware);

    if (!this.listeners('error').length) this.on('error', this.onerror);

    return (req, res) => {
      res.statusCode = 404;//如果接下来的中间件没有设置res 则默认为404状态
      const ctx = this.createContext(req, res); //创建ctx, 把request和response以及其它东西封装进去
      onFinished(res, ctx.onerror); // 当res结束或者报错时,调用onerror回调函数,这是一个npm库提供的方法
      fn(ctx).then(() => respond(ctx)).catch(ctx.onerror);
    };
  }

果不其然,它返回了一个方法!
接下来有两行代码最为重要,是koa2实现洋葱式调用的关键!!!
就是这两行
const fn = compose(this.middleware);
fn(ctx).then(() => respond(ctx)).catch(ctx.onerror);

compose是由一个名为koa-compose的库,它可以将多个中间件组合成洋葱式调用的对象,它就是今天的重点了!我们点进去看看

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] //fn只是一个函数声明,在下面调用
      if (i === middleware.length) fn = next 
      // 这里的next其实没有用 只是用来处理i ===middleware.length的情况 
      // next永远是空 这个next和下面的next是不一样的
      if (!fn) return Promise.resolve()
      try {
        // 因为fn()是一个async函数 返回一个promise对象 Promise.resolve()遇到promise对象的时候会原封不动的返回该对象
        // context就是封装好的ctx对象, next是你写在use里面的next
        // 执行fn()代码 就是执行自定的async函数 遇到内部await next()则会等待回调函数结束
        // 而这个回调函数递归调用下一个middleware 碰到下一个middleware的await next()则会继续调用下一个
        // 直到调用到最后一个 返回一个空的promise.resolve()对象 则先是最后一个middleware收到这个promise对象
        // 就执行await()下面的函数 最后一个中间件执行完毕后
        // 则会再到之前的中间件去执行
        return Promise.resolve(fn(context, function next () {

          /* 这个fn()就是
          next就是它的回调函数
          async (ctx,next) => {
             console.log("1-start");
             await next();
             console.log("1-end");
          } 
          注意这上面的函数是我们常写的函数 ctx就是context, next就是function next(){...}
          * fn(context, function next () {  //
              return dispatch(i + 1)
            })
          */
          return dispatch(i + 1)
        }))
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

是不是很复杂鸭。。 不过没关系 大家可以跟着我的注释一步一步的看下来,相信大家肯定会懂的!。
注意我们在applicaiton.js调用 dispatch的时候并没有传入next
fn(ctx).then(() => respond(ctx)).catch(ctx.onerror);
所以,在dispatch里面的形参next一直为null!

好啦 这就是最基本的koa2实现洋葱式调用的方法啦 希望大家看得懂,看不懂的可以在留言区多多跟我讨论

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容

  • 前言 Koa 是运行在 Node.js 中的 web 服务框架,小而美。 Koa2 是 Koa 框架的最新版本,K...
    let_Scott阅读 5,774评论 2 28
  • 框架提出的背景 ES6/7带来的变革 自ES6确定和ES7中async/await开始普及,Node的发展变得更加...
    宫若石阅读 8,499评论 1 14
  • Koa 必须使用 7.6 以上的版本。如果你的版本低于这个要求,就要先升级 Node。 基本用法 Koa 提供一个...
    Gukson666阅读 2,453评论 0 1
  • 源码结构 Koa的源码中主要为lib目录下的application.js, context.js, request...
    宫若石阅读 2,294评论 0 4
  • 图像识别往往包含数以百万计的参数,从头训练需要大量打好标签的图片,还需要大量的计算力(往往数百小时的GPU时间)。...
    zhyuzh3d阅读 3,314评论 4 11