在过去的一年里,我零散地阅读了很多开源项目的源代码,也有了一些体会。这里我想通过这篇文章来总结一下。这里我以Koa为例。其实前段时间看了Koa的源码,发现自己懂了一点点。有偏差,所以再过一遍。不得不说,看tj的代码真的很有收获,没有套路,代码优雅,设计优秀。更不用说评论了。总之,还是推荐他的项目Let'srunthroughtheexamples。Koa是一个网络框架。要想看它的源码就必须知道它的用法。Koa的文档也很简单。它在开头提供了一个例子:constKoa=require('koa');constapp=newKoa();app.use(asyncctx=>{ctx.body='HelloWorld';});app.listen(3000);这是最基本的webService,这个跑起来没问题。同样,文档也提供了Koa核心卖点中间件的基本用法:constKoa=require('koa');constapp=newKoa();//x-response-timeapp.use(async(ctx,next)=>{conststart=Date.now();awaitnext();constms=Date.now()-start;ctx.set('X-Response-Time',`${ms}ms`);});//loggerapp.use(async(ctx,next)=>{conststart=Date.now();awaitnext();constms=Date.now()-start;console.log(`${ctx.method}${ctx.url}-${ms}`);});//responseapp.use(asyncctx=>{ctx.body='HelloWorld';});app.listen(3000);上面的代码可能不符合我们之前写的js代码的常识,因为async/await会暂停犯罪现场,类似于同步。即代码在遇到awaitnext中间件时会跳出当前状态,执行接下来,终于回到原来的路径,依次执行下面的代码awaitnext。当然,这只是一种表象。其实就是递归返回Promise,后面会讲到。阅读目标。我们知道如何使用Koa,那么我们想了解这个框架有哪些呢?先看一下源码的目录结构:注意这个compose.js是我拉过来的,方便修改源码。其实就是一个附加包。Application.js绝对是一个入口文件。context.js的构造函数是ctxrequest.jsresponse.js,那么我们在阅读源码的时候需要一个目标。在本文中,我们假设目标是了解Koa的中间件原理。嗯,分析执行过程不错,目标也有了。让我们正式进入源码阅读状态。我们以最简单的示例代码作为入口,切入Koa的执行过程:constapp=newKoa();上面我们可以看到Koa是作为构造函数被引用的,那么我们再看一下WhatisexportedfromthefileApplication.js:module.exports=classApplicationextendsEmitter{//...}无疑是对应的,和一个类被导出。app.use(asyncctx=>{ctx.body='HelloWorld';});看上面的好像是对的,我们知道use指的是一个中间件,我们看看use是什么:use(fn){if(typeoffn!=='function')thrownewTypeError('中间件必须是一个函数!');if(isGeneratorFunction(fn)){deprecate('对生成器的支持将在v3中删除。'+'请参阅文档以获取有关如何转换旧中间件的示例'+'https://github.com/koajs/koa/blob/master/docs/migration.md');fn=转换(fn);}debug('使用%s',fn._name||fn.name||'-');这个.middleware.push(fn);归还这个;使用(fn){this.middleware.push(fn);归还这个;}emm这个就很清楚了,就是维护一个中间件数组middleware,别忘了我们这里的目标:Koa的中间件原理,既然已经找到了这个中间件数组,那么我们来看看它是怎么调用的。经过全局搜索,我们发现其实有一个方法使用了中间件:callback(){constfn=compose(this.middleware);if(!this.listeners('error').length)this.on('error',this.onerror);consthandleRequest=(req,res)=>{constctx=this.createContext(req,res);返回this.handleRequest(ctx,fn);};返回句柄请求;}从上面的代码可以看出,似乎有一个处理中间件的compose,我们似乎越来越接近真相functioncompose(middleware){/***@param{Object}context*@return{Promise}*@apipublic*/returnfunction(context,next){//最后调用的中间件#letindex=-1returndispatch(0)functiondispatch(i){index=iletfn=middleware[i]if(我===我ddleware.length)fn=nextif(!fn)returnPromise.resolve()try{returnPromise.resolve(fn(context,functionnext(){returndispatch(i+1)}))}catch(err){}returnPromise.reject(err)}}}}删除边界条件,错误处理compose.js的代码很短,但还是太长了。上一篇文章提到,删除边界条件和异常处理:letindex=-1returndispatch(0)functiondispatch(i){index=iletfn=middleware[i]if(!fn)returnPromise.resolve()返回Promise.resolve(fn(context,functionnext(){returndispatch(i+1)}))}}}就清楚多了,不就是递归遍历中间件吗?好像有点像express。猜想结论是一个大胆的假设。前面说了await会暂停执行,所以awaitnext好像是暂停在这里,然后递归调用中间件,然后递归中断,代码从promises一个个退出。看来这很葱。不知道emm是不是这样的。我想再写一篇文章进行比较。
