Koa是一个非常轻量级和优雅的节点应用程序开发框架。我在值班的时候看了KOA的源代码,把一些有趣的点写进文字里和大家分享。
洋葱中间件机制的实现原理
我们经常把koa中间件的执行机制比作剥洋葱。设计其执行顺序的好处是,我们不再需要手动管理请求和响应的业务执行流程,一个中间件中不同的请求和响应逻辑可以放在同一个函数中,这可以帮助我们大大简化代码。在了解其实现原理之前,先介绍一下koa的整体代码结构:
lib |-application . js |-context . js |-request . js |-response . js应用程序是整个应用程序的入口,提供koa构造函数和实例方法属性的定义。Context封装了koa ctx对象的原型对象,并提供了对响应和请求对象下许多属性方法的代理访问。request.js和response.js分别定义了ctx请求和响应属性的原型对象。
接下来,让我们看看application.js中的一段代码:
听着(.args){ debug(' listen ');const server=http . CreateServer(this . callback());return server.listen(.args);}回调(){ const fn=compose(this . middleware);if(!this . listener count(' error '). this . on(' error ',this . one error);const handleRequest=(req,RES)={ const CTX=this . create context(req,RES);返回this.handleRequest(ctx,fn);};返回handleRequest}handleRequest(ctx,fnMiddleware){ const RES=CTX . RES;res.statusCode=404const one rror=err=CTX . one rror(err);const handleResponse=()=response(CTX);onFinished(res,one rror);返回fnMiddleware(ctx)。然后(handleResponse)。catch(one rror);}以上代码展示了koa的基本原理。http.createServer封装在其实例方法listen中,然后在回调函数中执行koa中间件。在回调中,this.middleware是业务定义的中间件函数数组,compose是koa-compose模块提供的方法,集成了中间件,是构建koa洋葱中间件模型的秘诀。从handleRequest方法可以看出,compose方法的执行返回一个函数,函数的执行结果是一个承诺。接下来,让我们看看koa-compose是如何做到这一点的。它的源代码和一段koa中间件应用示例代码如下:
//撰写源代码函数撰写(中间件){if(!Array.isArray(中间件))抛出新类型错误('中间件堆栈必须是数组!')用于(中间件的常量fn){ if(fn的类型!=='function ')抛出新类型错误('中间件必须由函数组成!')} return function (context,next) { //最后调用的中间件# let index=-1 return dispatch(0)function dispatch(I){ if(I=index)return Promise . reject(new Error(' next()'调用了多次'))index=i let fn=中间件[i] if (i===中间件. length) fn=next if(!fn)返回Promise.resolve()尝试{ return promise . resolve(fn(context,dispatch.bind(null,I ^ 1));} catch (err) {return诺言。reject(err)} }/* * *中间件应用示例代码*/let KOA=require(' KOA ')let app=new KOA()app。use (async函数war E0 (CTX,下一个){ await setTimeout(function(){ console . log(' ware 0 request ')},0)next(). console . log(' ware 0 response ')})app . use(function ware 1(CTX,下一个){ console . log(' ware 1 request ')} next())/console . log(' ware 1 response ')//执行结果ware 0 request ware 1 response ware 0 response从上面的compose源代码中可以看出,每个中间件接受的下一个函数输入是定义的调度函数Dispatch接受中间件数组中下一个中间件的索引作为输入参数,它就像一个游标。每次执行下一个函数时,光标向后移动一位,得到middlaware数组中的下一个中间件函数执行,直到数组中的最后一个中间件,即使用app.use方法添加的最后一个中间件,依次执行回来。整个过程其实就是函数的调用栈,下一个函数的执行就是下一个中间件的执行,但是koa在函数的基础上增加了一层promise包,这样在中间件执行的时候就可以统一处理捕获到的异常。以上面写的应用示例代码为例,绘制函数执行调用栈示意图如下:
整个缀法的实现非常简单,核心代码只有17行,值得观看和学习。
生成器函数型中间件的执行
在koa的v1版本中,主流中间件支持生成器功能。在v2之后,它改为支持异步/等待模式。如果生成器仍在使用,koa将给出不推荐使用的提示。但是为了向后兼容,生成器函数类型的中间件目前还是可以执行的。koa内部使用koa-convert模块将生成器函数包装在一层中。请查看代码:
函数convert (mw) {//mw是生成器中间件if(类型为mw!=='function') {抛出新类型错误('中间件必须是函数')} if (mw.constructor.name!=='GeneratorFunction') { //假设是基于Promise的中间件返回mw } const converted=function (ctx,next) { return co.call(ctx,mw.call(ctx,Create generator(next))} converted。_ name=mw。_ name | | mw。name return converted } function * create generator(next){ return yield next()}从上面的代码可以看出,Koa-convert在生成器外部包装了一个函数,提供与其他中间件一致的接口,内部使用co模块执行生成器函数。这里我想谈谈co模块的原理。当生成器函数执行时,它不会立即执行其内部逻辑,而是返回一个测力计对象。然后通过调用遍历对象的下一个方法来执行它。生成器函数本质上是一个状态机。如果其中有多个yield表达式,则需要多次执行下一个方法才能完成函数体的执行。co模块的能力是实现生成器函数的自动执行,无需多次手动调用下一个方法。那么它是如何做到的呢?Co源代码如下:
函数co(gen){ var CTX=这个;var args=slice.call(参数,1);//我们将所有内容包装在一个承诺中,以避免承诺链,//这会导致内存泄漏错误。//参见https://github.com/tj/co/issues/180返回新的承诺(函数(解决,拒绝){ if(gen==' function '类型)gen=gen . apply(CTX,args);if(!gen | | gen .下一个的类型!=='函数')返回解析(性别);onFulfilled();/* * * @ param { Mixed } RES * @ return { Promise } * @ API private */function on completed(RES){ var ret;尝试{ ret=gen . next(RES);} catch(e){ return reject(e);} next(ret);}/* * * @ param { Error } err * @ return { Promise } * @ API private */function onRejected(err){ var ret;尝试{ ret=gen . throw(err);} catch(e){ return reject(e);} next(ret);} /** *获取生成器中的下一个值,*返回一个承诺* * @ param { Object } ret * @ ret { Promise } * @ API private */function next(ret){ if(ret。完成)ret解析(ret。值);//topcolore是一个函数,返回一个承诺示例定义变量值=topcolorse。呼叫(CTX,ret。值);if(值为colorse(值))返回值。然后(按预期完成);返回onRejected(新类型错误('您只能生成函数、承诺、生成器、数组或对象,',但以下对象已被传递: ' '字符串(ret。value)' ')));} });}从总裁源码来看,它先是手动执行了一次完成函数来触发发电机遍历器对象的然后方法,然后利用承诺的完成函数去自动完成剩余状态机的执行,在弹出时中利用遍历器对象的扔方法抛出执行上一次产量过程中遇到的异常,整个实现过程可以说是相当简洁优雅。
结语
通过上面的例子可以看出承诺的能量是非常强大的,koa的中间件实现和总裁模块的实现都是基于答应我,除了应用于日常的异步流程控制,在开发过程中我们还可以大大挖掘其潜力,帮助我们完成一些自动化程序工作流的事情。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。