express是一个相对完整的node服务端框架,自身集成了router。koa是一个相对纯粹的中间件web框架,基本只负责处理request和response、添加中间件和启动服务。其他的web相关功能都是通过中间件插拔式的集成安装进去的,所以koa的代码也非常简单,但是非常经典,体现了JavaScript函数式编程的精髓。koa还支持了Promise,Generator Function,让我们使用koa写异步编程代码十分的轻松。
一、koa中间件的添加
koa的中间件就是一个JavaScript的Function对象,参数是context和next,其中context是koa框架提供的一个对象,包含koa的request对象和response对象。next是一个koa提供给开发者的Function,用以执行下一个中间件。
-
中间件的添加,直接添加到application的middlewares数组中。
use(fn) { if (isGeneratorFunction(fn)) { fn = convert(fn); } this.middleware.push(fn); return this; }
如果中间件添加的是一个Generator Function,则将该function转换成一个普通的function。
use(fn) { if (isGeneratorFunction(fn)) { fn = convert(fn); } this.middleware.push(fn); return this; }
那么什么是Generator Function,中文翻译为迭代器对象生成函数,那什么又是迭代器对象呢?
-
迭代器对象
迭代器对象就是一个普通的Object类型的对象,提供一个next方法访问起内部迭代数据,返回 数据以及是否迭代完成。
// 一个简单的迭代器对象 var it = { var i = 0; next: function() { if (i < 10) { return { done: false, value: i }; } else { return { done: true, value: -1 }; } i ++; } } // 我们可以这么使用该迭代器对象 let result = it.next(); while (!result.done) { console.log(result.value); result = it.next(); }
如果我们自己编写迭代器对象,需要小心维护其内部状态,而且也比较麻烦,利用迭代器对象生成器我们可以很方便的创建迭代器对象。
- 迭代器对象生成器
function * makeIterator() { var i = 0; while(i < 10) { yield i++; } } var it = makeIterator();
显然通过这种方式构造迭代器对象要简洁得多。
我们知道koa中间件我们可以定义成一个迭代器对象生成器,内部用yield Promise的方式控制异步调用,如果我们自己调用迭代器对象生成函数生成的迭代器,那么我们就可以遍历里面的异步Promise,然后用then方法将它们连接起来按照顺序执行,但是这些事情现在都不需要我们自己做,伟大的co模块帮我们做了这件事情,这也是koa将一个迭代器生成函数convert成一个普通函数的方法。
-
将koa中的Generator中间件转换成一个普通的中间件
function convert (mw) { const converted = function (ctx, next) { return co.call( ctx, mw.call( ctx, (function * (next) { return yield next() })(next) )) } return converted }
有一说一,这段代码我似懂非懂。
转换之后,添加到middlewares数组中的所有中间件就是形如 function (ctx, next){}
二、koa中间件的执行
实际上,koa中间件执行最重要的环节就是将所有的中间件compose成一个。
function compose (middleware) {
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, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}