当前位置: 首页 > 科技观察

深入剖析Koa中间件实现机制

时间:2023-03-16 22:25:57 科技观察

Koa是基于Node.js的下一代Web开发框架,比Express轻量级,只有几百行源代码。与传统的中间件不同,Koa1.x中使用生成器来实现中间件,这需要开发者熟悉生成器和ES6中的Promise。Koa官方文档示例代码中,以yieldnext作为跳转信号,然后倒序执行中间件剩余的代码逻辑。这其中的逻辑很有意思,本文将深入分析。A节:Koa的中间件运行在co模块下,co可以将异步“变”为同步,从而可以用同步的方式编写异步代码,避免了Node.js中大量的回调嵌套。现在我们通过实现一个简单的co方法开始探索该机制。functionco(generator){letg=generator();letnext=function(data){letresult=g.next(data);if(result.done){return;};if(result.valueinstanceofPromise){result.value.then(函数(d){next(d);},函数(err){next(err);});}else{next();};};next();};首先需要了解generator的相关知识,接下来我们一步步分析这段代码:1.我们先定义一个参数为generator的co函数。2.当generator传入时(即app.use(function*(){...})),定义next方法实现generator的状态遍历(可以理解为状态机),因为每个遍历器都指向一个新的yield,返回{value:'Promise','done':'true/false'}等结构体的值,当done的值为false时,遍历状态完成并返回,如果为真,则继续遍历。内部的g.next(data)可以将之前yield的返回值传递给外部。3.同时,如果generator包含多个yield,遍历未完成(即result.value是一个Promise对象&&result.done===false),resolve()传过来的数据可以直接在接下来的then()方法中使用,即递归调用,直到result.done===true遍历结束退出。这里可能会有疑问。第一次调用next()方法时,data是未定义的。会导致错误吗?实际上,V8引擎在执行时,会自动忽略第一次调用next()时的参数,因此参数仅在第二次使用next()方法时有效。总之,co实现了Promise递归调用generator的next方法。SectionB:了解了co的运行原理之后,理解中间件的机制就容易多了。中间件实现了所谓的“逆序”执行,实际上就是每次调用use()方法时,生成器都被存储在一个数组(记为s)中进行存储。执行时,先定义一个执行索引(记为index)和一个跳转标记(记为turn,即yieldnext中的next),然后定义一个数组(记为gs)保存生成器函数对象。然后获取当前的中间件生成器,然后获取生成器的函数对象,将函数对象保存在gs数组中,然后执行生成器的next()方法。开始执行后,根据返回值进行不同的处理。如果标记为turn(即执行到yieldnext),则表示该跳转到下一个中??间件了。此时设置了index++,然后从数组g中获取下一个中间件,重复上一个中间件的执行流程。当执行的中间件没有yield且返回的done为true时,倒序执行。从之前保存生成器函数对象的gs数组中取出之前的生成器对象,然后执行生成器的next()方法,直到全部结束。我们打开Koa的application.js文件:/***Usethegivenmiddleware'fn'.**@param{GeneratorFunction}fn*@return{Application}self*@apipublic*/app.use=function(fn){if(!this.experimental){//es7asyncfunctionsarenotallowed,//sowehavetomakesurethat'fn'isageneratorfunctionassert(fn&&'GeneratorFunction'==fn.constructor.name,'app.use()requiresageneratorfunction');}debug('use%s',fn._name||fn.name||'-');this.middleware.push(fn);returnthis;};很明显,app.use()方法就是将generator传入this.middleware数组。其他部分的逻辑源码注释已经很清楚了,这里不再赘述。我们打开Koa-compose模块的index.js文件:){returnfunction*(next){if(!next)next=noop();vari=middleware.length;while(i--){next=middleware[i].call(this,next);}returnield*next;}}其中最关键的是while语句。取出并执行app.use()传入的生成器并以相反顺序存储在中间件中,并将每次生成器执行的结果(即generator()===iterator)作为参数传递给下一个(按照数组的顺序就是前一个)在生成器中,上一个生成器(第一个数组)执行后得到的下一个变量(也就是第一个生成器的迭代器),执行yield*next(即执行第一个生成器的迭代器)像链表一样将所有生成器串联起来。根据yield*的特点,yield*next会依次执行所有申请的next(类似于递归),这样就形成了所谓的“正序执行,逆序执行”的过程。从co到compose,代码只有几十行,但是组合的非常细腻美妙,值得细细品味。