0.背景
自从koa框架发布,已经有很多前端同行们对它的源码进行了解读。在知乎、掘金、Github上,已有不少文章讲了它的ctx等API实现、中间件机制概要、错误处理等细节,但对于中间件机制中的细节做逐行分析的文章还是比较少,本文将采用详细的逐行分析的策略,来讨论Koa中间件机制的细节。
PS:本次Koa源码分析基于2.7.0版本。
1. 从入口开始
大部分情况下使用Koa,都是这样的,假定我们的demo 入口文件叫app.js
// app.js
const Koa = require('koa');
const app = new Koa();
require在查找第三方模块时,会查找该模块下package.json文件的main字段。查看koa仓库目录下下package.json文件,可以看到模块暴露的出口是lib目录下的application.js文件
{
"main": "lib/application.js",
}
而lib/application文件中所暴露的出口
module.exports = class Application extends Emitter {}
可以看到,在app.js 中引用koa时,变量Koa就是指向该Application类。
2.如何响应请求
(已经了解Koa如何响应请求的同学,可以跳过本节,直接看第3节)
好,现在给app.js增加一点内容:监听3004端口,打印一行日志,返回
const Koa = require('koa');
const app = new Koa();
const final = (ctx, next) => {
console.log('Request-Start');
ctx.body = { text: 'Hello World' };
}
app.use(final);
app.listen(3004);
// 启动app.js,就可以看到返回的结果
以上这段代码中,ctx.body 如何实现并不是本文的重点,只要知道它的作用是设置响应体的数据,就可以了
在本节里,需要搞清楚的问题有两个:
- app.use 的作用是挂载中间件,它做了什么?
- app.listen 的作用是监听端口,它做了哪些工作?
回到刚刚的lib/application文件,可以看到Application上挂载了use方法
use(fn) {
// 类型判断
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
// 兼容v1版本的koa
if (isGeneratorFunction(fn)) {
deprecate('Support for generators will be removed in v3. ' +
'See the documentation for examples of how to convert old middleware ' +
'https://github.com/koajs/koa/blob/master/docs/migration.md');
fn = convert(fn);
}
// 中间省略部分无关代码
this.middleware.push(fn);
return this;
}
在官方文档里,中间件的类型是函数,因此use方法的第一行完成了参数类型的检查。
而第二段代码,则判断是否为Generator函数,如果是的话,就提示开发者Generator类型的中间件即将被废弃,并通过convert方法将该中间件的类型从Generator函数转换成普通函数。
为什么会有这么一段代码呢?因为在Koa的v1版本和v0版本,使用的异步控制方案是Generator+Promise+Co,因此将中间件定义成了Generator Function。但自从Koa v2版本起,它的异步控制方案就开始支持Async/Await,因此中间件也用普通函数就可以了。
这里用到了几个函数库,只要理解它们的作用和原理概要即可,有兴趣可以自行查看(但不看也不影响你理解后面的内容):
- isGeneratorFunction:判断是否为Generator函数,判断方法包括Object.prototype.call、Function.prototype.call、Object.getPrototypeOf等。
- deprecate:给出API即将被弃用的提示信息。
- convert:即koa-convert,作用是加入了一层函数嵌套,并使用Co自动执行原Generator函数
最后一段代码的作用是把传入的函数,push到this.middleware属性的尾部,而在Application对象的构造函数里,可以看到这么一行代码
this.middleware = [];
它是用来存储中间件的。
OK,中间件通过use方法存储好了&#x