koa中间件执行流程控制,代码非常精巧。通过下面洋葱模型的图片描述,记住这张图片。为什么会这样,这里举例说明constKoa=require('koa')constapp=newKoa()//fn1app.use(async(ctx,next)=>{console.log('fn1-1')next()console.log('fn1-2')})//fn2app.use(async(ctx,next)=>{console.log('fn2-1')next()console.log('fn2-2')})//fn3app.use(async(ctx,next)=>{console.log('fn3-1')next()console.log('fn3-2')})app.listen(4002)//fn1-1,fn2-1,fn3-1,fn3-2,fn2-2,fn1-2在上面的例子中,fn1-1,fn2-1,fn3-1,fn3-2,fn2-2,fn1-2,现在只知道调用next()函数会把控制流跳转到下一个中??间件,全部执行完之后再一步步执行前面的next后面的代码。这个根跟洋葱有很大的相似度(如果你愿意一层一层剥开我的心~~~)。探索但其背后的原理是什么??让我们一步步探索。首先是调用app.use(fn)这行代码,这行代码在源码里,去掉一些代码判断,看起来像这样constructor(){super();this.middleware=[];}use(fn){this.middleware.push(fn);returnthis;}就是把所有的函数push到一个中间件数组中,这个使用就是专门针对这个的。嗯,我知道使用的功能。执行use之后,我们的中间件里面有很多中间件函数。下面我们继续往下看。然后执行app.listen函数后,代码如下listen(...args){//创建服务器constserver=http.createServer(this.callback());returnserver.listen(...args);}我们看到有一个this.callback()执行函数,然后我们跳转到这个函数。callback(){//让我们看看这里constfn=compose(this.middleware);consthandleRequest=(req,res)=>{constctx=this.createContext(req,res);//请记住这个节点下面这行代码returnthis.handleRequest(ctx,fn);};returnhandleRequest;}在这个回调函数中,执行了compose函数,并将中间件数组作为参数传入。执行完compose函数,我们再看看compose里面有什么。compose函数一开始是指koa-compose模块。简化后,里面的代码如下。精简后只有20行代码。以下代码将在后面详细解释。functioncompose(middleware){returnfunction(context,next){letindex=-1returndispatch(0)functiondispatch(i){index=iletfn=middleware[i]if(i===middleware.length)fn=nextif(!fn)returnPromise.resolve()try{returnPromise.resolve(fn(context,functionnext(){returndispatch(i+1)}))}catch(err){返回Promise.reject(err)}}}}执行这个compose返回一个函数,也是核心函数。请注意,这是由上面的回调调用的。得到一个fn函数看上面的回调调用然后执行到this.handleRequest(ctx,fn);该函数使用ctx和fn(这是上面compose返回的函数)作为参数传入this.handleRequest。代码如下所示。handleRequest(ctx,fnMiddleware){returnfnMiddleware(ctx).then(handleResponse).catch(onerror);}就是在这里真正执行了compose返回的函数,传入了ctx,那我们继续看这个函数fnMiddleware(ctx),实际上看起来是这样的。function(context,next){//设置一个意外索引值letindex=-1//调用dispatch函数,一开始传入0returndispatch(0)//声明dispatch函数,functiondispatch(i){//将传入的值赋值给indexindex=i//取出middleware的第i个中间件函数赋值给fnletfn=middleware[i]//如果i等于middleware的长度,赋值nexttofnif(i===middleware.length)fn=next//如果fn为假returnreturnPromise.resolve()if(!fn)returnPromise.resolve()try{//返回一个Promise.resolve,执行fn同时,也是中间件,将next函数传入fnreturnPromise.resolve(fn(context,functionnext(){//递归调用自己returndispatch(i+1)}))}catch(err){returnPromise.reject(err)}}}上面的代码是这部分的精髓。我在这里详细说一下。首先定义一个index和dispatch函数,然后最开始调用dispatch(0)函数,将0赋值给index,然后从中间件数组中取出(我们例子中有3个中间件函数)去第0个中间件函数,赋值给fn,两次if都不满足条件后,执行returnPromise.resolve(fn(context,functionnext(){//递归调用自己returndispatch(i+1)}))这里执行了fn中间件函数,将ctx和functionnext(){returndispatch(i+1)})作为参数传入。此时代码如下app.use(async(ctx,next)=>{console.log('fn1-1')next()//执行传入的nextconsole.log('fn1-2')})执行这个函数会打印出fn1-1,然后执行next()函数。看前面这段代码,执行next()函数会调用dispatch(i+1),也就是callfn=middleware[1]就是第二个中间件。大概大家看到这里就明白了。然后进入第二个中间件执行fn,打印出fn2-1,继续执行next()函数,在next函数中继续调用dispatch(i+1),即fn=middleware[2]第三个middleware函数,打印Exitfn3-1,继续执行next()函数,会调用dispatch(i+1),即fn=middleware[3],这里注意,if(i===middleware.length)fn=next这里会满足这个条件,然后把next赋给fnwherenext就是这个fnMiddleware(ctx).then(handleResponse).catch(onerror);which是在调用的时候传入的,这里没有传入,所以此时fn是undefined,然后继续执行,直到if(!fn)returnPromise.resolve()返回空值,这就是下一次的执行结果第三个中间件,然后继续执行下一行打印出fn3-2,最后执行到fn2-2,再到fn1-2,整个中间件的执行过程。很像洋葱模型,一层层地进去,层层地出来。好了,整个中间件执行过程就是酱子了~~~最后安利一下博客:https://github.com/naihe138/n...
