Koa
前言
由于项目需要,要开发一个UDP服务器,没找到合适的第三方库,故需要自己实现
哪怎么个实现?我当时是没什么想法,但我们都是站在巨人的肩膀上的,所以这次我选择站在TJ大神的肩膀上
-
一开始是模仿的Express,使用尾调用的方式实现了过滤器和路由器特色
-
后面Koa出来了,NodeJS也原生支持部分ES6,就开始模仿Koa,加入了Generator和Promise,模仿了
this
的传递 -
后面Koa2也出来了,脱离了
co.js
,减少了对第三方库的依赖和更容易平滑升级到ES7
正文
使用一个新框架,我一般都是直接抄一个Demo跑一下,确定环境和依赖包的正常,以下是Koa官网的Demo
Demo:
var koa = require('koa')
var app = koa()
app.use(function*() {
this.body = 'Hello World'
})
app.listen(3000)
代码很少,只有app.use
和app.listen
app.use
用于加载中间件app.listen
启动服务器
继续找对应的代码实现
app.use:
app.use = function(fn){
if (!this.experimental) {
// es7 async functions are allowed
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;
};
很简单,就是往中间件的数组中添加中间件,当然这个中间件必须是generator函数
app.listen:
app.listen = function(){
debug('listen');
var server = http.createServer(this.callback());
return server.listen.apply(server, arguments);
};
这就是我们一开始接触NodeJS时的Demo,除了一个东西this.callback()
,这替代了function(req, res){}
,说明this.callback()
最终会返回一个供http.createServer
调用的function
继续看看Koa是怎么生成这个function(req, res){}
的
app.callback:
app.callback = function(){
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(req, res){
res.statusCode = 404;
var ctx = self.createContext(req, res);
onFinished(res, ctx.onerror);
fn.call(ctx).then(function () {
respond.call(ctx);
}).catch(ctx.onerror);
}
};
- 调用compse函数,将中间件数组传入,返回generator函数
- 使用
co.wrap
包装,生成代理方法,该方法将返回Promise对象 function(req, res){}
中执行fn.call(ctx),fn将完成所有中间件的处理
compose:
function compose(middleware){
return function *(next){
var i = middleware.length;
var prev = next || noop();
var curr;
while (i--) {
curr = middleware[i];
prev = curr.call(this, prev);
}
yield *prev;
}
}
function *noop(){}
curr.call(this, prev)
绑定this上下文对象,从上面代码中知道,这个this由外部特别指定的,然后在这再绑定到中间件中curr.call(this, prev)
绑定prev参数,这个参数总是下一个中间件,即第i个中间作为形参next
,传入第i-1的中间件中- 最终以
midddle[i-1](middle[i])
把所有中间件串联起来 - 所以每个next,实际上就是一个Generator的迭代器,在中间件中使用
yield next
来调用下一个中间件
总结
至此一个Koa的执行代码已经列出来了,不关注细节,把上述代码组合简化一下:
var server = http.createServer(function(req, res){
var ctx = self.createContext(req, res)
var gen = compose(this.middleware)
var prom = co.wrap(gen)
prom.call(ctx).then().catch()
}
})
server.listen()
Koa中间件功能的特性就在上面4行代码中了
self.createContext(req, res)
创建上下文compose(this.middleware)
将中间组合起来,最终返回一个Generator函数co.wrap(gen)
,对Generator函数进行包装,返回自动遍历Generator函数的代理方法,这执行代理方法将返回Promise对象prom.call(ctx)
把上下文判定到方法中,以便保证所有中间件中的this
均指向同一上下文对象