亚是一个非常轻量优雅的节点应用开发框架,趁着双十一值班的空当阅读了下其源代码,其中一些比较有意思的地方整理成文与大家分享一下。
<强>洋葱型中间件机制的实现原理强>
我们经常把高雅中间件的执行机制类比于剥洋葱,这样设计其执行顺序的好处是我们不再需要手动去管理请求和响应的业务执行流程,且一个中间件对于请求和响应的不同逻辑能够放在同一个函数中,可以帮助我们极大的简化代码。在了解其实现原理之前,先来介绍一下高雅的整体代码结构:
自由 |——application.js |——context.js |——request.js |——response.js
应用程序是整个应用的入口,提供高雅构造函数以及实例方法属性的定义.context封装了高雅ctx对象的原型对象,同时提供了对响应和请求对象下许多属性方法的代理访问,请求。js和响应。js分别定义了ctx请求和响应属性的原型对象。
接下来让我们来看application.js中的一段代码:
听(args) { 调试(“听”); const服务器=http.createServer (this.callback ()); 返回server.listen (…args); } 回调(){ const fn=组成(this.middleware); 如果(! this.listenerCount('错误'))。(“错误”,this.onerror); const handleRequest=(点播,res)=比;{ const ctx=reateContext(点播,res); 返回。handleRequest (ctx、fn); }; 返回handleRequest; } fnMiddleware handleRequest (ctx) { const res=ctx.res; res.statusCode=404; const>//组合源码 函数组成(中间件){ 如果(! Array.isArray(中间件))把新TypeError(中间件堆栈必须数组!) (const fn的中间件){ 如果(typeof fn !==昂?把新TypeError(中间件必须由功能!) } 返回函数(下)背景下,{//最后一个称为中间件# 让指数=1 返回调度(0) 函数调度(我){ 如果(我& lt;=指数)返回的承诺。拒绝(新的错误(多次“next()被称为')) 指数=我 让fn=中间件(我) 如果(我===middleware.length) fn=如果(fn)返回Promise.resolve () 尝试{ 回报的承诺。解决(fn(上下文,调度。绑定(null, i + 1))); }捕捉(err) { 返回Promise.reject(错) } } } }/* * *中间件应用示例代码 */让高雅=要求(“高雅”) 让应用程序=new高雅() 下一个app.use(异步函数ware0 (ctx) { 等待setTimeout(函数(){ 控制台。日志(“ware0请求”) }, 0) next () 控制台。日志(ware0响应) }) 接下来app.use(函数ware1 (ctx) { 控制台。日志(“ware1请求”) next () 控制台。日志(ware1响应) })//执行结果 ware0请求 ware1请求 ware1响应 ware0响应
从上述组成的源码可以看的出,每个中间件所接受的下一个函数入参都是在写返回函数中定义的派遣函数,分派接受下一个中间件在中间件来数组中的索引作为入参,该索引就像一个游标一样,每下一步当函数执行后,游标向后移一位,以获取middlaware数组中的下一个中间件函数进行执行,直到数组中最后一个中间件也就是使用app.use方法添加的最后一个中间件执行完毕之后再依次回溯执行。整个流程实际上就是函数的调用栈,下一个函数的执行就是下一个中间件的执行,只是高雅在函数基础上加了一层保证封装以便在中间件执行过程中能够将捕获到的异常进行统一处理。以上述编写的应用示例代码作为例子画出函数执行调用栈示意图如下:
整个合成方法的实现非常简洁,核心代码仅仅17行而已,还是非常值得围观学习的。
<强>发电机函数类型中间件的执行强>
v1版本的洋槐其中间件主流支持的是发电机函数,在v2之后改而支持async/等待模式,如果依旧使用发电机,高雅会给出一个弃用提示,但是为了向后兼容,目前发电机函数类型的中间件依然能够执行,洋桧木内部利用koa-convert模块对发电机函数进行了一层包装,请看代码:
函数转换(mw) {//兆瓦为发电机中间件 如果(typeof mw !==昂?{ 把新的TypeError(中间件必须是一个函数) } 如果(mw.constructor.name !==GeneratorFunction) {//假设它是基于承诺的中间件 返回兆瓦 } 下一个const转换=function (ctx) { 返回co.call (ctx, mw。调用(ctx, createGenerator(下))) } 转换。_name=兆瓦。_name | | mw.name 返回转换后的 } 函数* createGenerator(下){ 返回收益率next () }亚源码中承诺的解读