之前阅读了 co
的源码,其实一开始就是想看一下 koa
的源码,然后 koa
又是基于 co
构建的,所以先读了一下 co
,这次再来记录一下 koa
源码中的东西。
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
的构造函数,注意到 middleware
, context
,request
和 response
。Context
,Request
和 Response
都是 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);
}
这个函数就是检查 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;
};
调用 callback
函数返回的是一个形似 function(res, req) {}
的函数。这个返回的函数传给 http.createServer
。
让我们仔细看一下 callback
,首先,调用 compose
将 middleware
变为一个 Generator
函数,然后使用 co.wrap
将其变为一个返回 Promise
的函数。关于 co
的讨论,可以看我写的另一篇文章 阅读 co 源码。
接着保存 this
给 self
。
注意到 this.listeners('error').length
这段代码,Application.prototype
的原型是 EventEmitter.prototype
,因此拥有这个属性。
接着就是返回这个回调函数了,onFinished
是在一个 HTTP 请求关闭,结束或者出现错误的时候调用的,这里我们传入了 ctx.onerror
,createContext
所做的就是创建一个 Context
对象,然后创建 Request
和 Response
对象,并将它们进行一系列的关联。
可以看到下面还有一个 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
类(准确来说是用作一个原型)作为一个上下文,提供了到 Response
对象和 Request
对象的代理。
提供了一系列与请求相关的方法。
提供了一系列与响应相关的方法。
最后附上一个 Koa
调用的流程
Koa
是一个非常轻量级的 Web 框架,连路由都没有提供,去看它的源码的时候,发现居然只有4个文件,每个文件代码都不多,并且有许多注释,比较容易阅读。正是这样一个轻量级的框架,配合大量的中间件,可以构建出一个功能强大的应用,这也许就是 Koa 的魅力所在吧。