分析1.首先这是一个最简单的koa2入门示例。我将使用这个入门示例来演示koa2的洋葱模型。constKoa=require('koa');constapp=newKoa();app.use((ctx,next)=>{console.log("第一个中间件执行");next();});//第二个中间件app.use((ctx,next)=>{console.log("第二个中间件");})letr=app.listen(8080);console.log("服务器运行在8080");其中,app先调用两次use,然后开始监听端口,listen(...args){debug('listen');//当客户端发送请求时回调将被调用constserver=http.createServer(this.callback());返回服务器。听(...参数);}所以use是核心:use(fn){//...一些判断代码this.middleware.push(fn);归还这个;}从上面可以看出,外部的use函数是通过一个内部的中间件变量来记录的,然后就没有了。OK,现在客户端发送请求时,会在内部创建上下文对象,然后处理请求:callback(){constfn=compose(this.middleware);if(!this.listenerCount('error'))this.on('error',this.onerror);consthandleRequest=(req,res)=>{//创建上下文constctx=this.createContext(req,res);//处理请求returnthis.handleRequest(ctx,fn);};返回句柄请求;}处理请求handleRequest(ctx,fnMiddleware){constres=ctx.res;res.statusCode=404;constonerror=err=>ctx.onerror(err);consthandleResponse=()=>响应(ctx);onFinished(res,onerror);//核心,调用中间件,从这里可以看出我们use(fn)中的fn是一个promisereturnfnMiddleware(ctx).then(handleResponse).catch(onerror);}OK,到这里我们就知道,当浏览器发送请求时,koa的application对象会根据中间件调用compose生成另外一个函数fn,然后将contextctx传递给fn来执行这个函数。我们都知道top代码执行顺序是先打印第一个中间件执行,再打印第二个中间件执行,那么compose函数就需要保证这个机制。具体实现如下:=='function')thrownewTypeError('中间件必须由函数组成!')}/***@param{Object}context*@return{Promise}*@apipublic*/returnfunction(context,next){//最后调用的中间件#letindex=-1returndispatch(0)functiondispatch(i){if(i<=index)returnPromise.reject(newError('next()被多次调用'))index=iletfn=middleware[i]if(i===middleware.length)fn=nextif(!fn)returnPromise.resolve()try{returnPromise.resolve(fn(context,dispatch.bind(null,i+1)));}catch(err){returnPromise.reject(err)}}}}上面的代码虽然不多,但却是koa2实现洋葱模型最重要的部分。整个过程如下:client发出请求,调用fn(ctx),此时next为空=>dispatcher(0),获取第一个use的函数(middleware[0])执行这个函数,然后参数分别是:ctx,dispatch(1),thisdispatcher(1),也就是第一次使用时的next;执行next();第一次使用时在方法体中执行next(),相当于执行dispatcher(1),此时获取第二次使用的函数(middleware[1]),然后执行这个函数,参数为:ctx,dispatch(2),等等,稍后执行中间件。koa2的源码虽然小,但是原理巧妙,值得学习,正是因为它的体积小,我们更容易看源码进行学习。
