最近在写koa2相关例子,顺便看了下koa2的源码,下面是一些我的理解。node
koa1核心基于generator,可是严重依赖co的包装。koa2彻底不须要,基于async(其实质是generator的语法糖调用包装),在node v7 下可直接运行。
关于async和generator的语法,本文不作赘述。下面先建立一个koa实例,而后基于入口一步步分析。web
//koa code const Koa=require('koa'); const app=new Koa(); app.use(async function (ctx, next) { console.log('>> one'); await next(); console.log('<< one'); }); app.use(ctx => { ctx.body='hello world gcy'; }); app.listen('3000',function () { console.log('listening on port 3000'); });
说明 上面这段代码彷佛有些神秘,其实质是下面http module的封装调用。数组
// native code let http=require('http'); let server=http.createServer(function (req,res) { res.writeHead(200,{'Content-type':'text/plain'}); res.write('hello world gcy'); res.end(); }); //start service listen server.listen(8000,function () { console.log('listening on port 8000'); });
下面基于koa构造函数入口作进一步分析。promise
constructor() { super(); this.proxy = false; //建立一个空数组存放放middleware,洋葱流程的真相,下面会分析法到 this.middleware = []; //决定忽略的子域名数量,默认为2 this.subdomainOffset = 2; //处理环境变量 this.env = process.env.NODE_ENV || 'development'; //实例上挂载context,request,response this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); }
说明 上面是构造函数入口,启动入口以下服务器
//借用原生http.createServer,添加app.callback。 listen() { debug('listen'); const server = http.createServer(this.callback()); return server.listen.apply(server, arguments); }
说明 经过上面两个步骤一个完整的web服务器创建起来。对于监听接受到的请求解析处理,是经过callback函数,调用一系列中间件来完成。
下面分析中间件执行流程,我认为koa的主要内涵也就在这,因此作一下重点来论述。
首先引入经典中间件洋葱图,以便理解。架构
结合这幅图再看下面的代码app
////核心代码application 126 行 const fn = compose(this.middleware); return function (context, next) { let index = -1 return dispatch(0) function dispatch (i) { if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i let fn = middleware[i] if (i === middleware.length) fn = next //当即返回处于resolve状态promise实例,以便后续逻辑继续执行 if (!fn) return Promise.resolve() try { // await next(); //当fn里面执行这句话时,就会执行dispatch(i+1),致使洋葱执行过程 // 整个过程相似堆栈执行释放过程当中的的递归调用,虽然有差别,可借用类比思考其执行顺序流程 return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } // 核心代码application 136 行 return fn(ctx)[是一个当即状态的promise].then(handleResponse).catch(onerror); } }
若是结合注释看上述代码过程当中存在疑惑,可进一步参考下面的进行思考,反之忽略便可。dom
const Koa=require('koa'); const app=new Koa(); app.use(async function (ctx, next) { console.log('>> one'); await next(); console.log('<< one'); }); app.use(async function (ctx, next) { console.log('>> two'); ctx.body = 'two'; await next(); console.log('<< two'); }); app.use(async function (ctx, next) { console.log('>> three'); await next(); console.log('<< three'); }); //若是放到首部,不方便理解洋葱执行流程,由于没有调用next函数 app.use(ctx => { ctx.body='hello world gcy'; }); app.listen('3000',function () { console.log('listening on port 3000'); });
说明 koa基于中间价架构,核心简洁,除此以外还有一些其它相对重要的方法。koa
除此以外还有request和response的参数解析文件,由于逻辑简单,不作叙述。
虽然核心文件很少,可是其也require了很多包,下面列举几个比较重的,以做为示例。async
写这篇文章原由,是在写case的过程当中,同一解决方案下中间件选择的纠结症,尤为是在选择render template过程当中,为找其本质间差别,探寻到此。源码分析基于koa(version 2.2.0),通读源码以后,能够较清晰的开发或者使用别人的中间件,若是你有不一样的理解,欢迎留言交流。