玩转寇阿相思树之核心原理分析
寇阿相思树作为下一代网开发框架,不仅让我们体验到了异步/等待语法带来同步方式书写异步代码的酸爽,而且本身简洁的特点,更加利于开发者结合业务本身进行扩展。
本文从以下几个方面解读寇阿相思树源码:
封装创建应用程序函数扩展表示留数和请求中间件实现原理异常处理一、封装创建应用程序函数
利用开发可以很容易编写一个简单的应用程序:
const http=require(' http ')const server=http。createserver((req,res)={ //每一次请求处理的方法控制台。日志(请求。URL)RES .写头(200,{ ' Content-Type ' : ' text/plain ' })RES . end(' Hello NodeJS ')})服务器。听(8080)注意:当浏览器发送请求时,会附带请求/favicon.ico。
而寇阿相思树在封装创建应用程序的方法中主要执行了以下流程:
组织中间件(监听请求之前)生成语境上下文对象执行中间件执行默认响应方法或者异常处理方法//application.jslisten(.args){ const server=http。createserver(这。callback());return server.listen(.args);}回调(){ //组织中间件const fn=compose(这。中间件);//未监听异常处理,则采用默认的异常处理方法if(!这个。侦听器计数(“错误”)。这个。on('错误',这。一个错误);const handleRequest=(req,res)={ //生成语境上下文对象康斯特CTX=这个。创建上下文(请求、资源);返回this.handleRequest(ctx,fn);};返回handleRequest } handleRequest(CTX,fnMiddleware){ const RES=CTX。RES//默认状态码为404 res.statusCode=404//中间件执行完毕之后采用默认的错误与成功的处理方式const one rror=err=CTX。一个错误;const handleResponse=()=response(CTX);完成(res,一个错误);返回中间件.然后(手柄响应).捕获(一个rror);}二、扩展表示留数和请求
首先我们要知道开发中的表示留数和请求是http .输入消息和http .服务器响应的实例,那么就可以在开发中这样扩展请求和res:
对象。定义属性(http .IncomingMessage.prototype,{ query : { get(){返回查询字符串。解析(网址。解析(这个。网址).查询)} }})对象ServerResponse.prototype,{ js : { value : function(obj){ if(obj==' object ')的类型){ obj=JSON。这是。end(obj)} } })而寇阿相思树中则是自定义请求和反应对象,然后保持对表示留数和请求的引用,最后通过吸气剂和作曲者方法实现扩展。
//应用程序。jscreatecontext(req,RES){ const context=object。创建(这个。上下文);const request=上下文。请求=对象。创建(这个。请求);常量响应=上下文。响应=对象。创建(这个。回应);语境。app=请求。app=响应。app=this语境。req=请求。req=响应。req=req//保存原生请求对象语境。RES=请求。RES=响应。RES=RES//保存原生表示留数对象请求。CTX=回应。CTX=背景;request . response=response response . request=request context。起源平静=请求。原点间歇=请求。网址;语境。状态={ };//最终返回完整的语境上下文对象返回上下文;}所以在寇阿相思树中要区别这两组对象:
请求、响应: Koa扩展的对象res、req:节点原生对象//请求。jsget header(){返回这个。请求。标题;},设置标头{这个。请求。headers=val},此时已经可以采用这样的方式访问页眉属性:
ctx.request标头但是为了方便开发者调用这些属性和方法寇阿相思树将反应和请求中的属性和方法代理到语境上。
通过Object.defineProperty可以轻松的实现属性的代理:
函数访问(原型,目标,名称){对象。definepreproperty(proto,name,{ get () { return target[name] },set(value){ target[name]=value } })访问(上下文、请求、“标题”)而对于方法的代理,则需要注意这的指向:
函数方法(proto,target,name){ proto[name]=function(){ return target[name]。Apply (target,arguments)}}以上是属性代理和方法代理的核心代码,基本上是一个常用的例程。
代理这部分的详细源代码,可以查看节点-委托,但是这个包已经很长时间了,而且一些老方法已经被废除了。
上述过程的源代码中涉及了很多JavaScript的基础知识,比如原型继承和指向这个。对于基础薄弱的学生,首先需要了解这些基础知识。
三、中间件的实现原理
首先要明确的是,中间件不是NodeJS中的一个概念,而是从connect、express和koa框架中衍生出来的一个概念。
1.连接中间件的设计
在connect中,开发人员可以通过使用方法注册中间件:
函数使用(route,fn){ var handle=fn;var path=路由;//如果没有传入路由,默认为'/',基本上是routine if (typeof route!==' string '){ handle=route;路径='/';//存储中间件this . stack . push({ route : path,handle : handle });//为了链调用返回这个;}use方法在获取中间件的路由信息('/'默认情况下)和中间件的处理功能后,构造一个layer对象存储在队列中,就是上面代码中的栈。
连接中间件的执行流程主要由句柄和调用函数决定:
函数句柄(req,res,out){ var index=0;var stack=this.stack.函数next(err){ 0.//取出中间件var layer=stack[index] //终止条件if(!layer){ delay(done,err);返回;} var path=parseUrl(请求)。路径名| | '/';var route=layer.route//路由匹配规则if(路径。tolower case()。substr (0,路由。长度)!==route . tolowercase()){ return next(err);} .调用(layer.handle、route、err、req、res、next);} next();}在}handle函数中使用close函数next检查该层是否与当前路由匹配,匹配则执行该层上的中间件函数,否则继续检查下一层。
这里需要注意的是,next中检查路由的方式可能和想象的不太一样,所以在每个请求处理中都会执行默认路由为“/”的中间件。
函数调用(handle,route,err,req,res,next){ var arity=handle . length;var误差=errvar hasError=布尔值(err);尝试{if (hasError arity===4) {//错误处理中间件句柄(err,req,res,next);返回;} else if(!HasError arity 4) {//请求处理中间件句柄(req,res,next);返回;}} catch (e) {//记录错误=e;}//将错误传递给下一个(错误);} try/catch用于在通过调用方法执行中间件方法时捕捉错误。这里需要注意的是,call会根据是否有错误以及中间件功能的参数来决定是否执行错误处理中间件。而一旦捕捉到错误,下一个方法就会传递下去,所以即使普通的请求处理中间件在下一个中通过了路由匹配,仍然会被调用方法过滤掉。
以下是:层的处理流程图
以上是connect中间件设计的核心点,可以总结如下:
通过使用方法注册中间件;中间件的顺序执行通过next方法连接,需要手动调用,next中会进行路由匹配,过滤掉部分中间件;当中间件执行过程中出现异常时,next会携带异常过滤掉非错误处理中间件,这也是错误中间件比其他中间件多一个错误参数的原因;在请求处理周期中,需要手动调用res.end()来结束响应;2.Koa中间件的设计
Koa中间件和connect中间件在设计上有很大的区别:
Koa中间件的执行不需要匹配路由,所以注册中间件的每一个请求都会被执行。(当然,接下来还需要打电话);手动);在Koa中,通过继承事件和公开错误事件,开发人员可以自定义异常处理;中间件完成执行后自动调用Koa中的Res.end,避免了connect忘记调用res.end时用户得不到任何反馈,Koa中采用了Async/await语法,允许开发人员同步编写异步代码。当然,Koa也使用use方法注册中间件。与connect相比,省去了路由匹配的处理,非常简洁:
使用(fn){ this . middleware . push(fn);归还这个;}并且使用支持链式调用。
Koa中间件的执行流程主要由koa-compose中的compose函数完成:
函数合成(中间件){ if(!Array.isArray(中间件))抛出新类型错误('中间件堆栈必须是数组!')用于(中间件的常量fn){ if(fn的类型!=='function ')抛出新类型错误('中间件必须由函数组成!')}/* * * @ param { Object } context * @ return { Promise } * @ API public */return function(context,next){ let index=-1 return dispatch(0)function dispatch(I){ if(I=index)return Promise . reject(new Error(' next())多次调用'))index=i let fn=中间件[i] if (i==中间件. length) fn=next if(!Fn) returnpromise.resolve()尝试{//递归调用下一个中间件return promise . resolve(fn(context,dispatch.bind (null,I ^ 1)));} catch (err) {return诺言。reject (err)}}}看到通过connect和koa实现中间件的想法本质上是递归的,不难看出koa比connect更简单,主要是因为:
connect中提供了路由匹配功能,而Koa相当于connect中默认的“/”路径。当connect捕获到中间件异常时,它携带错误通过下一个验证中间件,直到错误处理中间件,而Koa用Promise封装中间件。一旦发生中间件异常,将直接触发拒绝状态,直接在catch of Promise中处理。以上是connect中间件和Koa中间件的实现原理。现在看这个Koa中间件的执行流程图,应该没有疑问吧?
四.异常处理
对于同步代码,try/catch可以轻松捕获异常,而connect中间件中的异常捕获是通过try/catch完成的。
对于异步代码,无法捕获try/catch。此时,一般可以构造Promise链来捕获最后一个catch方法中的错误,由Koa处理,并在catch方法中发送错误事件,这样开发人员就可以自定义异常处理逻辑。
this.app.emit('error ',err,this);我还谈到Koa使用async/await语法带来了以同步方式编写异步代码的酸味,也使错误处理更加自然:
//还可以自定义错误处理app。使用(async (CTX,下一个)={ try { await next();}抓住(呃){CTX。状态=错误。状态| | 500ctx。body=err}}) v .摘要
相信你会有新的认识,再次使用Koa的时候会更舒服,这也是分析Koa源代码的目的之一。
以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。
版权声明:玩转寇阿相思树之核心原理分析是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。