Abruzzi's Wall

Front-End Web Developer

Ghost only can help me to here...


如何理解koa中间件执行机制

前几天研究了TJ的koa/co4.x和一系列koa依赖的源码,在知乎上做出了人生首次回答(而且我真得再也不想去知乎回答技术问题了_(:з」∠)_),因此把文字搬到这里。

ES2015 Generator/Yield

关于Generator/Yield 这几篇文章已经写得足够清晰了:
The Basics Of ES6 Generators
Diving Deeper With ES6 Generators
Going Async With ES6 Generators
Getting Concurrent With ES6 Generators

Koa的运行机制

简单地画了一张图解释koa的处理流程(右键在新标签页中打开图片吧。。。懒):

在koa里定义的middleware均为generator function(包括内置在顶端的respond),这是为了能从任意middleware中容易地切换到其它middleware里(如果你是前端程序员,可以理解为浏览器捕获事件的capture和propagation过程,如果你是python程序员,可以理解为jungle的middleware机制,如果你是Java程序员,这种方式则是典型的切面编程)。

为了实现这种横穿多个middleware的特性,koa通过把后一个generator作为参数(koa里常用next)传入前一个generator实现(#见koa-compose源码,这也是为什么前两个middleware有next参数而最后一个没有)。

可以看到,在koa中yield的使用是在co,而co则是包装了generator/yield & Promise以模拟async/await,提供了一个更高层次的异步语法抽象。

koa在加载且合并所有的middleware之后,传递给co执行(确切地说是在http.createServer的callback触发后执行),co以图中所示逻辑不断拆解generator function,执行yield右侧固定的几种表达式(Array,Object,generator function,Promise,thunkify function),这5种表达式最终都会转化为Promise,以达到处理异步函数的目的。

co内部封装了onFulfilled和onRejected函数,当yield右侧的promise resolve之后,则会调用onFullfield函数,其包含了一条关键语句gen.next(res)#这句代码 用以给yield表达式赋值并执行下一次迭代。

koa通过上文的方式「深入」->「浅出」,最终在顶层的respond middleware里send response。

注:#thunk是co先前版本处理异步函数的方式,通过thunk可以将异步函数封装成curry,传入普通参数后形成仅需要callback参数的偏函数,以此简化callback调用代码(目前co中的thunk偏函数已经被#无情地Promise化了)。