阅读 Koa 源码

December 11, 2016

之前阅读了 co 的源码,其实一开始就是想看一下 koa 的源码,然后 koa 又是基于 co 构建的,所以先读了一下 co,这次再来记录一下 koa 源码中的东西。

compose

compose 方法返回一个 Generator 函数,将所有 middleware 串联起来。

function compose(middleware){
  return function *(next){
      // ...
  }
}

首先, 判断 next 是否为空,如果为空则将 next 赋值为 noop(空函数)。

接着逆序遍历 middleware,把 middleware 数组中的后一个产生的 iterator 作为前一个 middleware 的参数。

最后,yield * 第一个 next

理解这个函数,就明白在中间件中 yield next 是怎么工作的了,因为数组中后一个会把产生的 iterator (也就是 next)传给前一个。

function compose(middleware){
  return function *(next){
    if (!next) next = noop();

    var i = middleware.length;

    while (i--) {
      next = middleware[i].call(this, next);
    }

    return yield *next;
  }
}

Application

构造函数

先来看看 Application 的构造函数,注意到 middlewarecontextrequestresponseContextRequestResponse 都是 Koa 下面自定义的三个类。

function Application() {
  if (!(this instanceof Application)) return new Application;
  this.env = process.env.NODE_ENV || 'development';
  this.subdomainOffset = 2;
  this.middleware = [];
  this.proxy = false;
  this.context = Object.create(context);
  this.request = Object.create(request);
  this.response = Object.create(response);
}

Application#use

这个函数就是检查 fn 的合法性,然后把 fn 添加到 middleware 数组里面,返回 this 可以让这个函数链式调用。

app.use = function(fn){
  if (!this.experimental) {
    // es7 async functions are not allowed,
    // so we have to make sure that `fn` is a generator function
    assert(fn && 'GeneratorFunction' == fn.constructor.name, 'app.use() requires a generator function');
  }
  debug('use %s', fn._name || fn.name || '-');
  this.middleware.push(fn);
  return this;
};

Application#callback

调用 callback 函数返回的是一个形似 function(res, req) {} 的函数。这个返回的函数传给 http.createServer

让我们仔细看一下 callback,首先,调用 composemiddleware 变为一个 Generator 函数,然后使用 co.wrap 将其变为一个返回 Promise 的函数。关于 co 的讨论,可以看我写的另一篇文章 阅读 co 源码

接着保存 thisself

注意到 this.listeners('error').length 这段代码,Application.prototype 的原型是 EventEmitter.prototype,因此拥有这个属性。

接着就是返回这个回调函数了,onFinished 是在一个 HTTP 请求关闭,结束或者出现错误的时候调用的,这里我们传入了 ctx.onerrorcreateContext 所做的就是创建一个 Context 对象,然后创建 RequestResponse 对象,并将它们进行一系列的关联。

可以看到下面还有一个 respond.call(ctx)respond 是一个关于 Response 的帮助函数,最终结束掉响应。

app.callback = function(){
  if (this.experimental) {
    console.error('Experimental ES7 Async Function support is deprecated. Please look into Koa v2 as the middleware signature has changed.')
  }
  var fn = this.experimental
    ? compose_es7(this.middleware)
    : co.wrap(compose(this.middleware));
  var self = this;

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


  return function handleRequest(req, res){
    res.statusCode = 404;
    var ctx = self.createContext(req, res);
    onFinished(res, ctx.onerror);

    fn.call(ctx).then(function handleResponse() {

      respond.call(ctx);
    }).catch(ctx.onerror);
  }
};

Context

Context 类(准确来说是用作一个原型)作为一个上下文,提供了到 Response 对象和 Request 对象的代理。

Request

提供了一系列与请求相关的方法。

Response

提供了一系列与响应相关的方法。

总结

最后附上一个 Koa 调用的流程

Koa Flowcahrt

Koa 是一个非常轻量级的 Web 框架,连路由都没有提供,去看它的源码的时候,发现居然只有4个文件,每个文件代码都不多,并且有许多注释,比较容易阅读。正是这样一个轻量级的框架,配合大量的中间件,可以构建出一个功能强大的应用,这也许就是 Koa 的魅力所在吧。

# koa
# nodejs
# JavaScript
# Web
# 学习笔记
# 源码阅读
知识共享许可协议
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。
© 2015 - 2022 zhuscat
Hosted on Vercel