整体了解
koa源码主要有4个文件
1 | |____response.js |
其中response和request,context文件导出的对象,都是作为koa实例属性的原型对象,定义了相关对象的方法和属性。
比较重要的是application文件。
解析application文件
1. 从koa的使用开始讲起
var app = new koa()
这句代码生成了一个koa实例。
1 | app.use(async (ctx, next) => { |
上面代码定义了使用的中间件
app.listen(3004);
开启服务,监听端口3004。
2.源码解析
(1)整个文件导出的是一个构造函数
module.exports = class Application extends Emitter {}
appliation文件导出了一个构造函数,这个构造函数继承了node里面的Emitter类,所以它的实例可以有Emitter的方法。
(2)use方法
1 | use(fn) { |
use方法只干了一件事情,就是将中间件函数push进一个数组里面。
(3)listen函数
1 | listen(...args) { |
listen函数也很简单,就是执行了callback函数,得到一个服务器对象,再监听某个口,所以我们继续看callback函数
(4)callback函数
1 | callback() { |
callback函数比较重要。首先,它将中间件数组作为参数,传入compose函数中,compose函数会返回一个函数fn,fn函数有什么作用呢?只要执行fn函数,中间件函数就能依次被调用,前提是每个中间件都执行了next()。继续说callback函数的第二步,callback函数返回的一个函数handleRequest,handleRequest这个函数是用来处理每个请求的,当请求来临时,这个函数就会被调用。所以我们来看看handleRequest函数是怎样处理每个请求的:
请求来了之后,handleRequest会为每个请求生成一个全新的context对象,然后调用this.handleRequest来处理请求。
下面是this.handleRequest的代码:
1 | handleRequest(ctx, fnMiddleware) { |
注意fnMiddleware(ctx)这句代码,它就是执行了我们之前说的compose函数的返回函数。所以每个中间件 函数都可以将请求做一遍处理。而fnMiddleware(ctx)返回的是一个promise,所以调用then函数。在then函数里面调用了handleResponse, 也就是respond函数。
1 | function respond(ctx) { |
这个函数做的操作是返回一个response对象作为请求最终的响应。
(5)compose函数
另外我想说的一个函数是compose函数,也就是处理中间件数组的函数。
1 | function compose (middleware) { |
这个函数入参是中间件数组,出参是一个匿名函数。当调用这个匿名函数(称为a)的时候,就会调用dispatch函数,也就是会自动执行第一个中间件。当第一个中间件里面调用了next()的时候,就会再次调用dispatch函数,同理,如果第二个中间件里面调用了next()的时候,就会第三次调用dispatch….依次类推,直到所有的中间件都执行完了,便返回Promise.resolve()。
(6)为什么中间件需要写成async await函数或者是generator函数?
是为了保证中间件执行的顺序性,next函数之前的操作都要有结果了才能执行next函数,进入下一个中间件。
如果所有的中间件都是同步操作,写不写async await或者generator没有什么关系,但是如果中间件里面有异步操作,却没有将中间件函数写成async await或者generator的形式,那么在执行异步操作时,还没有得到异步操作的结果就有可能已经进入下一个中间件了。
1 | app.use((ctx, next) => { |
这里没有写async await,而fs.readFile是异步操作,这造成的结果是fileContent并不是文件内容,因为异步调用结果还没来得及返回就已经执行next了,这样的话,中间件的顺序就无法保证了。
1 | app.use(async (ctx, next) => { |
这里写了async await,那么await在得到异步操作结果之前会阻塞下面的代码,得到异步操作返回结果之后再继续执行下面的代码,从而保证了顺序性。