不得不说,相比较 express 而言,koa 的整体设计和代码实现显得更高级,更精炼;代码基于ES6 实现,支持generator(async await), 没有内置的路由实现和任何内置中间件,context 的设计也很是巧妙。
整体
一共只有4个文件:
request.js 和 response.js 没什么可说的,任何 web 框架都会提供req和res 的封装来简化处理。所以主要看一下 context.js 和 application.js的实现
// context.js /** * Response delegation. */ delegate(proto, 'res') .method('setHeader') /** * Request delegation. */ delegate(proto, 'req') .access('url') .setter('href') .getter('ip');
context 就是这类代码,主要功能就是在做代理,使用了 delegate 库。
简单说一下这里代理的含义,比如delegate(proto, 'res').method('setHeader') 这条语句的作用就是:当调用proto.setHeader时,会调用proto.res.setHeader 即,将proto的 setHeader方法代理到proto的res属性上,其它类似。
// application.js 中部分代码 constructor() { super() this.middleware = [] this.context = Object.create(context) } use(fn) { this.middleware.push(fn) } listen(...args) { debug('listen') const server = http.createServer(this.callback()); return server.listen(...args); } callback() { // 这里即中间件处理代码 const fn = compose(this.middleware); const handleRequest = (req, res) => { // ctx 是koa的精髓之一, req, res上的很多方法代理到了ctx上, 基于 ctx 很多问题处理更加方便 const ctx = this.createContext(req, res); return this.handleRequest(ctx, fn); }; return handleRequest; } handleRequest(ctx, fnMiddleware) { ctx.statusCode = 404; const onerror = err => ctx.onerror(err); const handleResponse = () => respond(ctx); return fnMiddleware(ctx).then(handleResponse).catch(onerror); }
同样的在listen方法中创建 web 服务, 没有使用 express 那么绕的方式,const server = http.createServer(this.callback()); 用this.callback()生成 web 服务的处理程序
callback 函数返回handleRequest, 所以真正的处理程序是this.handleRequest(ctx, fn)
中间件处理
构造函数 constructor 中维护全局中间件数组 this.middleware和全局的this.context 实例(源码中还有request,response对象和一些其他辅助属性)。和 express 不同,因为没有router的实现,所有this.middleware 中就是普通的”中间件“函数而非复杂的 layer 实例,
this.handleRequest(ctx, fn); 中 ctx 为第一个参数,fn = compose(this.middleware) 作为第二个参数, handleRequest 会调用 fnMiddleware(ctx).then(handleResponse).catch(onerror); 所以中间处理的关键在compose方法, 它是一个独立的包koa-compose, 把它拿了出来看一下里面的内容:
// compose.js 'use strict' module.exports = compose function compose (middleware) { return function (context, next) { let index = -1 return dispatch(0) function dispatch (i) { 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) } } } }
和express中的next 是不是很像,只不过他是promise形式的,因为要支持异步,所以理解起来就稍微麻烦点:每个中间件是一个async (ctx, next) => {}, 执行后返回的是一个promise, 第二个参数 next的值为 dispatch.bind(null, i + 1) , 用于传递”中间件“的执行,一个个中间件向里执行,直到最后一个中间件执行完,resolve 掉,它前一个”中间件“接着执行 await next() 后的代码,然后 resolve 掉,在不断向前直到第一个”中间件“ resolve掉,最终使得最外层的promise resolve掉。
这里和express很不同的一点就是koa的响应的处理并不在"中间件"中,而是在中间件执行完返回的promise resolve后:
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
通过 handleResponse 最后对响应做处理,”中间件“会设置ctx.body, handleResponse也会主要处理 ctx.body ,所以 koa 的”洋葱圈“模型才会成立,await next()后的代码也会影响到最后的响应。
至此,koa的中间件实现就完成了。
redux
不得不说,redux 的设计思想和源码实现真的是漂亮,整体代码量不多,网上已经随处可见redux的源码解析,我就不细说了。不过还是要推荐一波官网对中间件部分的叙述 : redux-middleware