当前位置: 首页 > 后端技术 > Node.js

koa-compose-application.js

时间:2023-04-03 15:51:32 Node.js

forkoa源码阅读koa-compose/application.jskoa-composeforkoa源码阅读方便理解//这个英文。我还将翻译一个wave//Probablyreturnallmiddlewarecombinationstoacompletemiddleware/***Compose`middleware`return*afullyvalidmiddlewareconclusion*ofallthosewhichpassed.**@param{Array}middleware*@return{Function}*@apipublic*///开始他的魔法//middleware传入一个数组并返回一个函数functioncompose(middleware){//如果不是数组andIf数组的元素不是函数,抛出类型错误!=='function')thrownewTypeError('Middlewaremustbecomposedoffunctions!')}/***@param{Object}context*@return{Promise}*@apipublic*///上下文对象返回apromise//注意上面所谓的return函数其实就是匿名函数return。这个匿名函数返回一个promise//tj对异步回调的处理方案太强了returnfunction(context,next){//最后调用的中间件#letindex=-1//初始下标是-1returndispatch(0)functiondispatch(i){//如果传入的i为负数且<=-1,则返回一个Promise.reject并给出错误信息//这里是保证next只被调用一次//next执行一次后,索引值会beChange,因为下面有赋值//所以执行next两次就会报这个错。状态被拒绝if(i<=index)returnPromise.reject(newError('next()calledmultipletimes'))//index=i;index=i//取出一个中间件letfn=middleware[i]//这个处理可以在application中找到。事实上,next是未定义的//即在Promise链的最深处是一个Promise。resolveif(i===middleware.length)fn=next//如果中间件已经遍历到末尾。所以。至此我们终于returnPromise.resolve()//ThisPromiserelatedstateif(!fn)returnPromise.resolve()//trycatch保证在Promise的情况下可以正常捕获错误。//这是一个递归。//我们知道Promise.resolve的状态是resolve//而当Promise.resolve(value)的值是一个promise。//返回传入的承诺//vard=Promise.reject('test')//varz=Promise.resolve(d)//z==dtry{returnPromise.resolve(fn(context,functionnext(){returndispatch(i+1)}))}catch(err){returnPromise.reject(err)}}}}如果你用过koa。现在你应该明白下洋葱体的奥秘了吧。application.jsconstisGeneratorFunction=require('is-generator-function');constdebug=require('debug')('koa:application');constonFinished=require('on-finished');constresponse=require('./response');constcompose=require('koa-compose');constisJSON=require('koa-is-json');constcontext=require('./context');constrequest=require('./request');conststatuses=require('statuses');constCookies=require('cookies');constaccepts=require('accepts');constEmitter=require('events');constassert=require('assert');constStream=require('stream');consthttp=require('http');constonly=require('only');constconvert=require('koa-convert');constdeprecate=require('depd')('koa');//头部的导入模块前面的文章有所描述/***公开`Application`类。*继承自`Emitter.prototype`。*///appliaction继承了nodejs的事件模块module.exports=classApplicationextendsEmitter{/***初始化一个新的`Application`。**@apipublic*///初始化constructor(){超级();this.proxy=false;这个.middleware=[];this.subdomainOffset=2;//这个很常用,区分开发和在线模式this.env=process.env.NODE_ENV||'发展';//前面文章中提到的文件this.context=Object.create(context);this.request=Object.create(请求);this.response=Object.create(response);}/***简写:**http.createServer(app.callback()).listen(...)**@param{Mixed}...*@return{Server}*@apipublic*///这个块使用了一个小细节//http.createServer(app.callback()).listen(...)//其实就是上面的形式listen(...args){debug('听');//callback是很重要的一点。constserver=http.createServer(this.callback());返回server.listen(...args);}/***返回JSON表示。*我们只关心显示设置。**@return{Object}*@apipublic*/toJSON(){returnonly(this,['subdomainOffset','proxy','env']);}/***检查实现。**@return{Object}*@apipublic*/inspect(){returnthis.toJSON();}/***使用给定的中间件`fn`。**旧式中间件将被转换。**@param{Function}fn*@return{Application}self*@apipublic*///当我们使用koa.//varkoa=require('koa')();//koa.use(asyncfunction(context,next){})//这也相当于一个简单的中间件。use(fn){//判断传入类型需要是函数if(typeoffn!=='function')thrownewTypeError('middlewaremustbeafunction!');//如果是生成器函数,那么我们应该使用conver来转换它。//这个库在我之前的文章中提到过。此处未提及if(isGeneratorFunction(fn)){deprecate('对生成器的支持将在v3中删除。'+'有关如何转换旧中间件的示例,请参阅文档'+'https://github.com/koajs/koa/blob/master/docs/migration.md');fn=转换(fn);}debug('使用%s',fn._name||fn.name||'-');//inthis.middlewarepushedintothisfunction//在这里,请想一想koa-compose传入的是什么this.middleware.push(fn);//提供链调用returnthis;}/***为节点的本地http服务器返回请求处理程序回调*。**@return{Function}*@apipublic*///当callback用于createServer回调时。那么它应该是function(req,res){constfn=compose(this.middleware)的函数callback();//使用compose来处理我们的中间件数组//返回一个promiseif(!this.listeners('error').length)this.on('error',this.onerror);//这是handleRequestconsthandleRequest=(req,res)=>{//默认statuscODEres.statusCode=404;//创建上下文对象constctx=this.createContext(req,res);//onerror函数constonerror=err=>ctx.onerror(err);consthandleResponse=()=>respond(ctx);//这个也说了onFinished(res,onerror);//composemiddlewares数组返回一个promise//因为我们内部有一个trycatch来显式抛出状态,所以我们可以在链上捕获//fn(ctx,undefined);返回fn(ctx).then(handleResponse).catch(onerror);};返回句柄请求;}/***初始化一个新的上下文。**@apiprivate*///为我们的中间件创建ctx对象createContext(req,res){//下面是一波错综复杂的交替引用传递constcontext=Object.create(this.context);常量请求=context.request=Object.create(this.request);constresponse=context.response=Object.create(this.response);//context---app--->request---app--->response---app--->this//---req--->request---req--->response---req--->req(原生可读流)//---res--->request---res--->response---res--->res(原生可写流)//---ctx--->response---ctx---->context//---response->response//response--request--->request//这块注意区分reqrequestresresponsecontext.app=request.app=response.app=this;context.req=request.req=response.req=req;context.res=request.res=response.res=res;request.ctx=response.ctx=上下文;请求.响应=响应;response.request=请求;//下面是一些方便调用的属性//并且记住我们的context.js文件中有很多属性方法委托那么意思是我们可以直接访问//ctx.body==>ctx.response.bodycontext.originalUrl=request.originalUrl=req.url;context.cookies=newCookies(req,res,{keys:this.keys,secure:request.secure});request.ip=request.ips[0]||req.socket.remoteAddress||'';context.accept=request.accept=接受(请求);context.state={};返回上下文;}/***默认错误处理程序。**@param{Error}err*@apiprivate*///默认错误处理onerror(err){assert(errinstanceofError,`non-errorthrown:${err}`);如果(404==err.status||err.expose)返回;如果(this.silent)返回;constmsg=err.stack||err.toString();控制台.错误();console.error(msg.replace(/^/gm,''));控制台.错误();}};/***响应助手。*///可以想成res响应体辅助函数respond(ctx){//允许绕过koaif(false===ctx.respond)return;constres=ctx.res;如果(!ctx.writable)返回;让body=ctx.body;常量代码=ctx.状态;//忽略正文if(statuses.empty[code]){//剥离标题ctx.body=null;返回res.end();}if('HEAD'==ctx.method){if(!res.headersSent&&isJSON(body)){ctx.length=Buffer.byteLength(JSON.stringify(body));}返回res.end();}//状态正文if(null==body){body=ctx.message||字符串(代码);如果(!res.headersSent){ctx.type='text';ctx.length=Buffer.byteLength(body);}返回res.end(body);}//responses//三种处理bufferstringstreamif(Buffer.isBuffer(body))returnres.end(body);if('string'==typeofbody)returnres.end(body);if(bodyinstanceofStream)returnbody.pipe(res);//body:json//bodyJSON字符串序列化句柄ctx.body是一个对象body=JSON.stringify(body);如果(!res.headersSent){ctx.length=Buffer.byteLength(body);}res.end(body);}重点是koa-compose的封装很重要。感觉tj这方面的功力实在是太强了。异步,nextchain组装方法。两者都很强大。下篇内容继续看几个koa中间件。并在文中写一个小demo,至此koa系列就结束了。如果这篇文章对你有帮助。不妨给我点赞/或收藏这篇文章。因为那是对我最好的鼓励。如果有什么不对的地方,不妨告诉我谢谢参考:koa-compose地址koa地址