node的兴起,随之产生的各类框架也如雨后春笋般的出现。现在主流的各类node框架,主要包括koa、express、iojs等。这些框架得以应用广泛,也离不开丰富的中间件资源。一般做一个项目,我们都需要use很多的中间件进来。那么这些框架,它内部是怎样来执行这些中间件的呢?今天我们就通过对 koa 和 connect 的源码分析来一探究竟。
Connect实现分析
我们先来看个hello world的栗子,connect到底是怎么使用的:
|
|
这个栗子中有两个中间件,分别是logger
和hello
,并通过use
方法逐个加载进去。你可能已经注意到,两个中间件函数参数个数不一样,logger
多了一个next
参数,在输出完日志后执行了这个函数。那么我们来看下在connect内部到底是怎么实现中间件逐个执行的,这个next
到底是个什么。
|
|
这段代码是use
方法的实现,我们每添加一个中间件,use
都会把它塞到一个事先定义好的空数组中app.stack = [];
。接下来我们看下,它是怎么实现持续调用这个数组中的中间件的。
|
|
在handle
内部有个next
方法,首先是从stack
数组中逐个取出中间件,由上面的栗子可知现在这个layer.handle
就是logger
函数。关键是看第12行的代码,在调用call
方法的时候,把当前的next
方法作为参数递归地传递了下去。在call
方法内部执行了logger
函数,由我们上面的logger
中间件实现可知,我们在logger
内部调用了next
方法。这样我们就可以继续执行下一个中间件。同理,下一个取到的是hello
中间件,因为在hello
内部没有做next
的传递,所以也就不会再执行下去了。
由上所得,一个是中间件的调用顺序和它use
的顺序一致;还有就是要想能够持续触发中间件,前面的中间件必须手动执行next
方法。接下来我们来看看koa又是怎么实现的呢?
Koa实现分析
我们还是用上面那个栗子,现在把它改成koa的实现方式:
|
|
同样也是use
了两个中间件,不同的是这里不再是普通的Function
了,而必须是generatorFunction
。和上面栗子一样,第一个中间件也有一个next
参数,第二个没有。接下来我们来看下koa中use
都做了些什么呢?
|
|
和connect的实现一样,都是把中间件塞到一个事先定义好的空数组中this.middleware = [];
。当我们调用listen
方法的时候,开始了我们的处理,我们直接来看app.callback
的实现:
|
|
先是把respond
塞到数组队列的最前面,这里respond
也是一个generatorFunction
。关键是这个compose
到底是做了什么呢,其实这个函数才是这里的重点,我们来看下它的源码:
|
|
我们知道middleware
是一个中间件的数组队列,而每一个中间件都是一个generatorFunction
。首先prev
初始为一个空的generator
对象,在while
循环中,从最后一个中间件开始调用,并把这个空的generator
对象做参数传进去,同时把最后一个中间件的generator
对象赋值给prev
。作i--
执行倒数第二个中间件,注意这时把倒数第一个的generator
对象传递给了倒数第二个generatorFunction
。并且prev
被重新赋值为倒数第二个中间件的generator
对象。以此递推,到最后这个prev
的值为这个middleware
数组排在第一个中间件的generator
对象。也就是我们上面提到的respond
的generator
对象。循环完成后,执行yield *prev
,这个是代理delegating yield
,执行到respond
内部,在respond
头部,我们看到又是一个代理yield *next;
。这里的next
是什么呢?前面我们说过,在作while
循环的时候,后面一个中间件的generator
对象会被传递到前一个genderatorFunction
里来,所以这里的next
就是respond
后面一个中间件的generator
对象,这样才能持续的执行下去。所以,我们添加的中间件内部,都要加上yield *next
这一句,这样才能一直执行下去。
小结
通过 connect 和 koa 的分析得知,要想持续执行所有的中间件,都需要我们自己手动调用next
方法,connect中使用next()
,koa中使用yield *next
。我们把需要手工调用才能持续执行后续调用的方法,叫做尾触发。详情可以查看《深入浅出Nodejs》一书的第四章。可能刚接触的时候觉得很绕,但当真正理解里面的实现原理后,才能体会到它的魅力所在,鹅妹子婴!!!