NodeJS 错误处理最棒实践

Node.js,NodeJS的错误处理令人优伤,在非常长的一段时间里,大量的一无所能被舍弃不管。不过要想建立3个康泰的Node.js程序就亟须正确的拍卖这几个错误,而且那并简单学。假使您其实未有耐心,那就径直绕过长篇大论跳到“总计”部分吗。

原文

这篇文章会答应NodeJS初学者的几何题材:

  • 自笔者写的函数里怎么时候该抛出尤其,哪天该传给callback,什么日期触发伊夫ntEmitter等等。
  • 本人的函数对参数该做出怎么着的尽管?笔者应该检查尤其具体的约束么?例如参数是不是非空,是还是不是超越零,是还是不是看起来像个IP地址,等之类。
  • 本身该怎么处理那么些不符合预期的参数?作者是理所应当抛出一个不行,照旧把错误传递给贰个callback。
  • 自个儿该怎么在先后里分别分裂的老大(比如“请求错误”和“服务不可用”)?
  • 自笔者怎么才能提供充分的新闻让调用者知晓错误细节。
  • 自家该怎么处理未料想的失误?小编是应当用 try/catch ,domains
    照旧其余什么措施呢?

那篇作品能够划分成互相为根基的多少个部分:

  • 背景:希望你所具有的文化。
  • 操作失败和程序员的失误:介绍二种为主的不得了。
  • 编写制定新函数的推行:关于怎么让函数发生有用报错的为主尺度。
  • 编写制定新函数的现实推荐:编写能发生有用报错的、健壮的函数需求的八个反省列表
  • 例子:以connect函数为例的文书档案和题词。
  • 总结:全文至此的见解放区救济总会计。
  • 附录:Error对象属性约定:用规范方法提供3个特性列表,以提供越来越多消息。

背景

正文假若:

你早已纯熟了JavaScript、Java、 Python、 C++
只怕类似的言语中尤其的概念,而且你知道抛出非常和破获万分是何等看头。
您熟习怎么用NodeJS编写代码。你选择异步操作的时候会很轻松,并能用callback(err,result)方式去做到异步操作。你得清楚下边包车型地铁代码不可能正确处理非常的由来是怎样[脚注1]

function myApiFunc(callback)
{
/*
 * This pattern does NOT work!
 */
try {
  doSomeAsynchronousOperation(function (err) {
    if (err)
      throw (err);
    /* continue as normal */
  });
} catch (ex) {
  callback(ex);
}
}

你还要熟练三种传递错误的法子: – 作为特别抛出。 –
把错误传给一个callback,这么些函数就是为了处理格外和处理异步操作重临结果的。

  • 在伊芙ntEmitter上接触贰个Error事件。

接下去大家会详细谈论那三种艺术。那篇小说不借使你领悟其余有关domains的文化。

最终,你应有知道在JavaScript里,错误和十分是有分别的。错误是Error的贰个实例。错误被创设并且一贯传送给另三个函数只怕被抛出。假使二个荒唐被抛出了那么它就改为了2个极度[脚注2]。举个例子:

throw new Error(‘something bad happened’);

不过选取2个荒唐而不抛出也是能够的

callback(new Error(‘something bad happened’));

这种用法更加宽广,因为在NodeJS里,超越三分之一的荒唐都以异步的。实际上,try/catch唯一常用的是在JSON.parse和好像验证用户输入的地点。接下来我们会看出,其实很少要捕获一个异步函数里的不得了。那或多或少和Java,C++,以及别的严重依赖非凡的言语很不均等。

操作战败和程序员的失误

把错误分成两大类很有用[脚注3]:

  • 操作退步是毋庸置疑编写的次第在运营时发生的一无所长。它并不是程序的Bug,反而不时是别的难点:系统自身(内部存款和储蓄器不足可能打开文件数过多),系统布局(未有到达远程主机的路由),互联网难点(端口挂起),远程服务(500错误,连接失利)。例子如下:
  • 老是不到服务器
  • 不知所措解析主机名
  • 不行的用户输入
  • 请求超时
  • 服务器重临500
  • 套接字被挂起
  • 系统内部存款和储蓄器不足

  • 程序员失误是程序里的Bug。这么些错误往往能够经过改动代码幸免。它们永远都无法被有效的拍卖。

  • 读取 undefined 的二天性质
  • 调用异步函数没有点名回调
  • 该传对象的时候传了3个字符串
  • 该传IP地址的时候传了3个目的

人人把操作失败和程序员的失误都称呼“错误”,但实则它们很不雷同。操作战败是具有科学的先后应该处理的荒唐意况,只要被稳当处理它们不肯定会预示着Bug或是严重的题材。“文件找不到”是3个操作战败,但是它并不一定意味着哪儿出错了。它大概只是意味着着程序1旦想用四个文件得事先创造它。

与之相反,程序员失误是彻彻底底的Bug。那些情况下您会犯错:忘记验证用户输入,敲错了变量名,诸如此类。那样的不当历来就没办法被处理,假使得以,那就表示你用处理错误的代码代替了失误的代码。

诸如此类的区分很重点:操作失利是先后符合规律化操作的一片段。而由程序员的失误则是Bug。

1些时候,你会在一个Root问题里同时蒙受操作战败和程序员的失误。HTTP服务器访问了未定义的变量时奔溃了,那是程序员的失误。当前连日着的客户端会在先后崩溃的同时来看贰个ECONNRESET错误,在NodeJS里一般会被报成“Socket
Hang-up”。对客户端的话,那是三个不相干的操作退步,
那是因为不易的客户端必须处理服务器宕机只怕网络中断的事态。

类似的,如若不处理好操作退步,
那本人正是叁个失误。举个例子,即使程序想要连接服务器,可是获得二个ECONNREFUSED错误,而以此顺序尚未监听套接字上的error事件,然后程序崩溃了,那是程序员的失误。连接断开是操作失利(因为那是其余八个不易的次序在系统的网络只怕别的模块出难点时都会经历的),假如它不被正确处理,那它便是2个失误。

明亮操作战败和程序员失误的两样,
是搞清怎么传递极度和处理十分的基本功。通晓了那点再持续往下读。

拍卖操作退步

就像是质量和黑河难点一样,错误处理并不是足以凭空加到3个并未有别的错误处理的次序中的。你未曾艺术在一个集中的地点处理全数的要命,就像是你不能够在2个聚齐的地点化解全体的习性难点。你得思考别的会促成失利的代码(比如打开文件,连接服务器,Fork子进度等)恐怕爆发的结果。包涵为啥出错,错误背后的来由。之后会谈起,可是关键在于错误处理的粒度要细,因为哪儿出错和怎么出错决定了震慑大小和心路。

你可能会发觉在栈的某几层不断地处理相同的一无所能。那是因为尾部除了向上层传递错误,上层再向它的上层传递错误以外,底层没有做其余有意义的业务。平常,只有顶层的调用者知道科学的回复是如何,是重试操作,报告给用户依然别的。可是那并不代表,你应当把持有的荒谬全都丢给顶层的回调函数。因为,顶层的回调函数不知道发生错误的上下文,不知道怎么操作已经成功实施,哪些操作实际退步了。

作者们来更具体有个别。对于3个加以的一无可取,你能够做这一个业务:

  • 直接处理。有的时候该做哪些很清楚。假若你在尝试打开日志文件的时候得到了二个ENOENT错误,很有希望你是首先次打开这几个文件,你要做的正是率先创制它。更有意思的例证是,你维护着到服务器(比如数据库)的持久连接,然后蒙受了1个“socket hang-up”的十三分。那平时意味着要么远端要么本地的互连网失利了。很多时候那种指鹿为马是暂且的,所以大多数场合下你得重新连接来化解难点。(那和接下来的重试非常的小学一年级样,因为在您拿走这几个指鹿为马的时候不肯定有操作正在进行)

  • 把失误扩散到客户端。如果你不精晓怎么处理那些那三个,最简单易行的方式正是剖腹藏珠你正在举行的操作,清理全部伊始的,然后把错误传递给客户端。(怎么传递非凡是此外一遍事了,接下去会探讨)。这种措施符合错误长期内不大概化解的情景。比如,用户提交了不科学的JSON,你再分析三遍是没什么帮忙的。

  • 重试操作。对于那1个来自网络和远程服务的不当,有的时候重试操作就足以消除难点。比如,远程服务再次来到了503(服务不足用错误),你恐怕会在几秒种后重试。假设明确要重试,你应当清楚的用文书档案记录下将会反复重试,重试多少次直到退步,以及五回重试的间隔。 class=”Apple-converted-space”> 此外,不要老是都若是必要重试。如若在栈中很深的地点(比如,被三个客户端调用,而越发客户端被此外叁个由用户操作的客户端控制),那种气象下高速战败让客户端去重试会越来越好。倘若栈中的每1层都认为必要重试,用户最终会等待更加长的时间,因为每一层都尚未发现到下层同时也在品味。

  • 直接崩溃。对于那几个本不容许发生的不当,可能由程序员失误造成的荒唐(比如不能连接受同1程序里的本地套接字),能够记下一个不当日志然后一贯崩溃。其余的例如内部存款和储蓄器不足那种漏洞非常多,是JavaScript那般的脚本语言不大概处理的,崩溃是尤其客观的。(即使如此,在child_process.exec如此的分离的操作里,获得ENOMEM不当,或然那2个你能够合理合法处理的谬误时,你应有思量这么做)。在您无法须要让管理员做修复的时候,你也得以一直崩溃。借使您用光了独具的公文讲述符可能未有访问安顿文件的权力,那种场馆下您如何都做不了,只可以等有个别用户登录系统把东西修好。

  • 笔录错误,其余什么都不做。有的时候你哪些都做不了,未有操作能够重试大概遗弃,未有任何理由崩溃掉应用程序。举个例子吗,你用DNS跟踪了一组远程服务,结果有二个DNS失败了。除了记录一条日志并且继续利用剩下的劳动以外,你怎么样都做不了。不过,你至少得记录点什么(凡事都有例外。假使那种场馆每秒发生几千次,而你又无奈处理,这每趟发生都记录恐怕就不值得了,不过要周期性的记录)。

(未有办法)处理程序员的失误

对于程序员的失误未有啥好做的。从概念上看,一段本该工作的代码坏掉了(比如变量名敲错),你无法用愈多的代码再去修复它。1旦您如此做了,你就利用错误处理的代码代替了失误的代码。

些微人倾向从程序员的失误中平复,也正是让日前的操作失利,不过后续处理请求。那种做法不引进。怀念这么的情景:原始代码里有贰个弄错是没考虑到某种特殊意况。你怎么规定那个难点不会影响其余请求呢?假如别的的伸手共享了有些状态(服务器,套接字,数据库连接池等),有大幅的或是别的请求会不正规。

特出的例证是REST服务器(比如用Restify搭的),要是有3个伸手处理函数抛出了三个ReferenceError(比如,变量名打错)。继续运行下去很有肯能会招致惨重的Bug,而且极其难发现。例如:

  1. 壹对请求间共享的景观恐怕会被变成nullundefined要么其余无效值,结果正是下贰个呼吁也战败了。
  2. 数据库(或其它)连接或然会被外泄,下跌了能够并行处理的呼吁数量。最终只剩余多少个可用连接会很坏,将促成请求由并行变成串行被处理。
  3. 更糟的是, postgres 连接会被留在打开的请求事务里。那会导致 postgres
    “持有”表中某一行的旧值,因为它对那几个工作可知。这几个题材会设有某个周,造成表无界定的增高,后续的伸手全都被拖慢了,从几微秒到几分钟[脚注4]。尽管那么些题材和
    postgres
    紧凑有关,不过它很好的证实了程序员多个不难的失误会让应用程序陷入一种十分可怕的气象。
  4. 连接会停留在已注脚的情况,并且被持续的连接使用。结果就是在伸手里搞错了用户。
  5. 套接字会平昔打开着。1般景观下NodeJS 会在多个空余的套接字上采取两分钟的逾期,但以此值能够覆盖,那将会败露3个文件讲述符。假诺那种状态频频爆发,程序会因为用光了具有的文件讲述符而强退。就算不掩盖这一个超时时间,客户端会挂两分钟直到
    “hang-up” 错误的产生。这两分钟的推移会让难点棘手处理和调剂。
  6. 诸多内部存款和储蓄器引用会被残留。那会招致泄露,进而导致内部存款和储蓄器耗尽,GC供给的岁月扩展,最终品质小幅度降低。那点11分难调节和测试,而且很必要技术与导致造成泄漏的失误联系起来。

最棒的从失误恢复生机的主意是随即崩溃。你应有用二个restarter 来运营你的先后,在奔溃的时候自动重启。即便restarter 准备妥贴,崩溃是失误来近年来最快的回涨可信赖服务的办法。

奔溃应用程序唯一的负面影响是无休止的客户端权且被打搅,不过切记:

  • 从概念上看,那些错误属于Bug。大家并不是在商讨不奇怪的类别可能互连网错误,而是程序里其实存在的Bug。它们应该在线上很稀有,并且是调剂和修补的最高优先级。
  • 地点探究的各个事态里,请求未有供给肯定得成功做到。请求大概得逞做到,只怕让服务器再一次崩溃,恐怕以某种分明的方法不科学的完成,或许以一种很难调节和测试的法子不当的扫尾了。
  • 在3个完备的分布式系统里,客户端必须能够因而重连和重试来拍卖服务端的荒唐。不管 class=”Apple-converted-space”> NodeJS class=”Apple-converted-space”> 应用程序是或不是被允许崩溃,网络和系统的败诉已经是2个实际了。
  • 若是你的线上代码如此反复地崩溃让连接断开变成了难题,那么正真的标题是您的服务器Bug太多了,而不是因为你挑选出错就完蛋。

只要出现服务器日常崩溃导致客户端频仍掉线的题材,你应该把经验集中在促成服务器崩溃的Bug上,把它们成为可捕获的不胜,而不是在代码显然有标题标状态下尽恐怕地制止崩溃。调节和测试这类难点最佳的格局是,把
NodeJS 配置成出现未捕获极度时把基本文印出来。在 GNU/Linux 大概 基于
illumos
的类别上利用那么些基本文件,你不光翻开应用崩溃时的堆栈记录,还足以看来传递给函数的参数和其余的
JavaScript 对象,甚至是这么些在闭包里引用的变量。尽管未有安插 code
dumps,你也足以用堆栈音讯和日志来开始拍卖难题。

末段,记住程序员在劳动器端的失误会造成客户端的操作败北,还有客户端必须处理好服务器端的奔溃和网络中断。那不只是理论,而是实际发生在线上环境里。

编排函数的实施

咱俩早就探究了怎么处理格外,那么当你在编辑新的函数的时候,怎么才能向调用者传递错误吧?

最最根本的一点是为您的函数写好文书档案,包罗它接受的参数(附上项目和其余约束),重返值,大概发生的荒唐,以及那个错误意味着怎么样。 假使你不清楚会导致怎么着错误只怕不驾驭错误的意思,那您的应用程序符合规律干活就是2个戏剧性。 所以,当您编写新的函数的时候,一定要告诉调用者恐怕爆发什么样不当和不当的意思。

Throw, Callback 还是 EventEmitter
函数有三种基本的传递错误的格局。

  • throw以协同的点子传送万分–也正是在函数被调用处的同一的上下文。假设调用者(也许调用者的调用者)用了try/catch,则足够能够捕获。要是具有的调用者都并未有用,那么程序平日状态下会崩溃(极度也说不定会被domains如故经过级的uncaughtException捕捉到,详见下文)。

  • Callback是最基础的异步传递事件的1种形式。用户传进来三个函数(callback),之后当有些异步操作达成后调用那些 class=”Apple-converted-space”> callback。通常 class=”Apple-converted-space”> callback class=”Apple-converted-space”> 会以callback(err,result)的款式被调用,那种气象下, class=”Apple-converted-space”> err和 class=”Apple-converted-space”> result必然有八个是非空的,取决于操作是打响依旧败诉。

  • 更扑朔迷离的景色是,函数未有用 class=”Apple-converted-space”> Callback class=”Apple-converted-space”> 而是回到1个 class=”Apple-converted-space”> EventEmitter class=”Apple-converted-space”> 对象,调用者要求监听那几个指标的 class=”Apple-converted-space”> error事件。那种方法在二种情况下很有用。

  • 当您在做二个恐怕会发生七个谬误或多少个结果的纷纭操作的时候。比如,有二个请求一边从数据库取多少一边把多少发送回客户端,而不是伺机全部的结果1块抵达。在那几个例子里,未有用
    callback,而是再次回到了二个 class=”Apple-converted-space”> EventEmitter,各样结果会接触三个row class=”Apple-converted-space”> 事件,当有着结果发送实现后会触发end事件,出现错误时会触发1个error事件。

用在那多少个拥有复杂性状态机的对象上,那些指标往往伴随着大量的异步事件。例如,一个套接字是二个伊夫ntEmitter,它大概会接触“connect“,”end“,”timeout“,”drain“,”close“事件。那样,很当然地能够把”error“作为其它壹种能够被触发的事件。在这种地方下,清楚精晓”error“还有其他事件曾几何时被触发很重点,同时被触发的还有啥风云(例如”close“),触发的次第,还有套接字是或不是在终止的时候处于倒闭状态。

在大部气象下,我们会把 callback 和 event emitter
归到同3个“异步错误传递”篮子里。假诺您有传递异步错误的须要,你平凡假如用当中的1种而不是还要选拔。

那就是说,何时用throw,哪一天用callback,曾几何时又用 EventEmitter 呢?那取决两件事:

  • 那是操作失利依旧程序员的失误?
  • 以此函数本人是同台的要么异步的。

直到当前,最广大的例证是在异步函数里发生了操作失利。在大多数状态下,你必要写三个以回调函数作为参数的函数,然后你会把十一分传递给那些回调函数。那种措施行事的很好,并且被大规模运用。例子可参照
NodeJS 的fs模块。借使您的场景比地点那个还复杂,那么你或者就得换用
伊芙ntEmitter 了,但是你也依然在用异步方式传送这几个破绽百出。

帮忙常见的3个例证是像JSON.parse 那样的函数同步发生了三个丰硕。对这么些函数而言,倘诺遇上操作失利(比如无效输入),你得用同步的方法传送它。你可以抛出(越发广泛)恐怕再次来到它。

对于给定的函数,固然有3个异步传递的不得了,那么具有的不得了都应有被异步传递。大概有那样的景况,请求壹到来您就驾驭它会失败,并且理解不是因为程序员的失误。恐怕的情事是您缓存了回到给近期央求的谬误。尽管你驾驭请求一定战败,不过你要么应当用异步的主意传递它。

通用的轨道就是 您即能够协同传递错误(抛出),也足以异步传递错误(通过传给多个回调函数只怕触发伊芙ntEmitter的
error事件)
,可是并非同时使用。以那种措施,用户处理分外的时候能够采取用回调函数如故用try/catch,不过不须求三种都用。具体用哪三个在于格外是怎么传递的,那一点得在文书档案里证实清楚。

差那么一点忘了程序员的失误。纪念一下,它们其实是Bug。在函数初步通过检查参数的品种(或是其余约束)就能够被马上发现。1个战败的例证是,某人调用了3个异步的函数,然则尚未传到调函数。你应该即刻把这些错抛出,因为程序已经出错而在这么些点上最佳的调节和测试的机会正是赢得二个仓房音讯,假设有基本音讯就越来越好了。

因为程序员的失误永远不该被拍卖,上边提到的调用者只可以用try/catch恐怕回调函数(可能伊夫ntEmitter)个中壹种处理非凡的守则并未因为这条意见而变更。如若您想掌握更加多,请见上面包车型客车(不要)处理程序员的失误。

下表以 NodeJS
大旨模块的常见函数为例,做了三个总计,大概依照各类难题应运而生的频率来排列:

函数 类型 错误 错误类型 传递方式 调用者
fs.stat 异步 file not found 操作失败 callback handle
JSON.parse 同步 bad user input 操作失败 throw try/catch
fs.stat 异步 null for filename 失误 throw none (crash)

异步函数里出现操作错误的例子(第3行)是最常见的。在共同函数里发出操作退步(第1行)比较少见,除非是验证用户输入。程序员失误(第二行)除非是在付出环境下,不然永远都不应有出现。

捉弄:程序员失误仍旧操作退步?

您怎么了然是程序员的失误如故操作败北呢?很粗大略,你本身来定义并且记在文书档案里,包蕴允许什么项目标函数,怎么着打断它的实施。假设您取得的不胜不是文书档案里能接受的,那正是多个程序员失误。假如在文书档案里写明接受可是近来处理不了的,那就是3个操作失利。

您得用你的判断力去决定你想做到多严俊,可是大家会给您肯定的观点。具体有个别,想象有个函数叫做“connect”,它接受三个IP地址和二个回调函数作为参数,那几个回调函数会在成功恐怕战败的时候被调用。今后假若用户传进来2个显眼不是IP地址的参数,比如“bob”,那年你有二种采用:

  • 在文书档案里写清楚只接受有效的IPV四的地方,当用户传进来“bob”的时候抛出贰个不行。强烈推荐那种做法。
  • 在文书档案里写上接受其余string类型的参数。假诺用户传的是“bob”,触发二个异步错误指明不能够连接到“bob”这个IP地址。

那二种方法和大家地点提到的有关操作战败和程序员失误的引导标准是如出壹辙的。你控制了如此的输入算是程序员的失误依旧操作战败。平常,用户输入的校验是很松的,为了评释那一点,能够看Date.parse那几个事例,它承受广大类其余输入。可是对于超越四分一别样函数,大家强烈建议你偏向更严刻而不是更松。你的主次尤其疑忌用户的本心(使用隐式的更换,无论是JavaScript语言本身这么做依旧有意为之),就一发不难猜错。本意是想让开发者在动用的时候不要越发具体,结果却消耗了住户好多少个时辰在Debug上。再说了,借使您觉得那是个好主意,你也能够在今后的版本里让函数不那么严厉,但是假设您发现由于预计用户的打算导致了累累讨厌的bug,要修复它的时候想维持包容性就非常的小或者了。

故而一旦多少个值怎么都不容许是卓有成效的(本该是string却收获3个undefined,本该是string类型的IP但鲜明不是),你应当在文档里写明是那不允许的同时及时抛出3个尤其。只要您在文书档案里写的明驾驭白,那那正是三个程序员的失误而不是操作战败。马上抛出能够把Bug带来的损失降到最小,并且保留了开发者可以用来调节那个题材的音信(例如,调用堆栈,要是用基本文件还足以得到参数和内部存款和储蓄器分布)。

那么 domains 和 process.on('uncaughtException') 呢?

操作失利总是能够被出示的编写制定所拍卖的:捕获1个不行,在回调里处理错误,大概处理EventEmitter的“error”事件等等。Domains以及经过级其余‘uncaughtException’重要是用来从未料到的次序错误苏醒的。由于地点大家所谈论的原因,那两种方法都不鼓励。

编辑新函数的现实提议

大家曾经切磋了层见迭出指引规范,以往让大家切实有个别。

  1. 你的函数做怎么着得很明亮。
    那点非凡主要。每一个接口函数的文书档案都要很清晰的辨证: – 预期参数 –
    参数的门类 – 参数的额外约束(例如,必须是行得通的IP地址)
    假使中间有一些不科学可能衰竭,那正是1个程序员的失误,你应当登时抛出来。
    其它,你还要记下:

    • 调用者也许会遇见的操作失利(以及它们的name)
    • 怎么处理操作战败(例如是抛出,传给回调函数,照旧被
      伊夫ntEmitter 发出)
    • 返回值
  2. 使用 Error 对象或它的子类,并且完毕 Error
    的商谈。
    您的富有错误要么使用Error 类要么使用它的子类。你应有提供name和message属性,stack也是(注意准确)。

  3. 在先后里透过 Error 的 name属性区分不一样的一无所长。
    当您想要知道不当是何连串型的时候,用name属性。
    JavaScript内置的供您重用的名字包罗“RangeError”(参数超出有效限制)和“TypeError”(参数类型错误)。而HTTP万分,常常会用RFC钦命的名字,比如“BadRequestError”大概“ServiceUnavailableError”。

  4. 不用想着给每一种东西都取3个新的名字。假如你能够只用三个简便的InvalidArgumentError,就不用分成
    InvalidHostnameError,InvalidIpAddressError,InvalidDnsError等等,你要做的是透过扩充属性来表达那里出了难点(下边会讲到)。

  5. 用详细的性质来增强 Error 对象。
    举个例子,假诺碰着无效参数,把 propertyName 设成参数的名字,把 propertyValue 设成传进来的值。若是不能够连到服务器,用 remoteIp 属性指明尝试连接到的
    IP。假如产生1个连串错误,在syscal 属性里安装是哪个系统调用,并把错误代码放到errno属性里。具体你能够查看附录,看有哪些样例属性能够用。
    至少须求这几个属性:

name:用于在先后里分别众多的一无可取类型(例如参数不合规和连接退步)

message:一个供人类阅读的荒谬音信。对可能读到这条音信的人的话那应该已经足足完整。假诺您从更底层的地方传递了一个荒谬,你应当加上部分音信来证实您在做什么。怎么包装相当请往下看。

stack:1般来讲不要随便骚扰堆栈音信。甚至毫无增强它。V8引擎唯有在那脾个性被读取的时候才会真的去运算,以此小幅提升处理相当时候的性质。假诺你读完再去增强它,结果就会多付出代价,哪怕调用者并不须求堆栈音讯。

您还应当在错误消息里提供足够的新闻,那样调用者不用分析你的一无所长就足以新建自己的一无是处。它们可能会当地化那几个错误消息,也大概想要把多量的失实聚集到一块儿,再恐怕用区别的秘籍展现错误消息(比如在网页上的多个报表里,恐怕高亮突显用户错误输入的字段)。

  1. 假定你传递3个底层的失实给调用者,考虑先包装一下。
    不时会发现3个异步函数funcA调用其余一个异步函数funcB,如果funcB抛出了一个荒唐,希望funcA也抛出一模一样的荒唐。(请留意,第贰局地并不再而三跟在率先片段之后。有的时候funcA会重复尝试。有的时候又希望funcA不经意错误因为无事可做。但在此处,大家只谈谈funcA直白重回funcB荒谬的意况)

在那个例子里,能够设想包装这一个指鹿为马而不是间接重临它。包装的意趣是继承抛出贰个带有底层音讯的新的百般,并且带上圈套前层的上下文。用 verror 那几个包能够非常的粗略的到位那一点。

举个例子,假若有一个函数叫做 fetchConfig,这一个函数会到三当中距离的数据库取得服务器的安顿。你也许会在服务器运维的时候调用那个函数。整个工艺流程看起来是如此的:

一.加载布署 一.1 连接数据库 1.1.一 解析数据库服务器的DNS主机名 一.一.2建立三个到数据库服务器的TCP连接 一.一.三 向数据库服务器认证 1.二 发送DB请求
①.3 解析再次回到结果 壹.四 加载配置 二 起首拍卖请求

假诺在运维时出了三个标题连接不到数据库服务器。假设老是在 一.壹.2的时候因为未有到主机的路由而破产了,每一个层都不加处理地都把越发向上抛出给调用者。你恐怕会看到如此的百般新闻:

myserver: Error: connect ECONNREFUSED

那显著没什么大用。

1派,假如每一层都把下一层重临的不得了包装一下,你能够获取更多的新闻:

myserver: failed to start up: failed to load configuration: failed to
connect to database server: failed to connect to 127.0.0.1 port 1234:
connect ECONNREFUSED。

您只怕会想跳过里面几层的封装来拿到一条不那么充满学究气息的音信:

myserver: failed to load configuration: connection refused from
database at 127.0.0.1 port 1234.

可是话又说回去,报错的时候详见一点总比音信不够要好。

万1你说了算封装多少个相当了,有几件工作要思考:

  • 保持原来的非凡完整不变,有限帮助当调用者想要直接用的时候尾部的尤其还可用。

  • 依然用原来的名字,要么展现地采用一个更有意义的名字。例如,最尾巴部分是
    NodeJS 报的三个简短的Error,但在步骤第11中学得以是个 IntializationError
    。(不过只要程序可以透过其它的质量区分,不要觉得有义务取三个新的名字)

  • 保存原错误的具有属性。在适用的情状下坚实message本性(可是毫无在原本的万分上改动)。浅拷贝其余的像是syscallerrno那类的性质。最佳是间接拷贝除了 class=”Apple-converted-space”> namemessagestack以外的保有属性,而不是硬编码等待拷贝的性质列表。不要理睬stack,因为即正是读取它也是绝对昂贵的。假使调用者想要叁个联结后的库房,它应当遍历错误原因并打字与印刷每三个不当的堆栈。

在Joyent,我们运用verror 这么些模块来封装错误,因为它的语法简洁。写那篇小说的时候,它还不可能支撑地点的具有机能,可是会被增添以期援助。

例子

考虑有如此的3个函数,那几个函数会异步地延续到三个IPv肆地址的TCP端口。我们经过例子来看文档怎么写:

/*
* Make a TCP connection to the given IPv4 address.  Arguments:
*
*    ip4addr        a string representing a valid IPv4 address
*
*    tcpPort        a positive integer representing a valid TCP port
*
*    timeout        a positive integer denoting the number of milliseconds
*                   to wait for a response from the remote server before
*                   considering the connection to have failed.
*
*    callback       invoked when the connection succeeds or fails.  Upon
*                   success, callback is invoked as callback(null, socket),
*                   where `socket` is a Node net.Socket object.  Upon failure,
*                   callback is invoked as callback(err) instead.
*
* This function may fail for several reasons:
*
*    SystemError    For "connection refused" and "host unreachable" and other
*                   errors returned by the connect(2) system call.  For these
*                   errors, err.errno will be set to the actual errno symbolic
*                   name.
*
*    TimeoutError   Emitted if "timeout" milliseconds elapse without
*                   successfully completing the connection.
*
* All errors will have the conventional "remoteIp" and "remotePort" properties.
* After any error, any socket that was created will be closed.
*/
function connect(ip4addr, tcpPort, timeout, callback)
{
assert.equal(typeof (ip4addr), 'string',
    "argument 'ip4addr' must be a string");
assert.ok(net.isIPv4(ip4addr),
    "argument 'ip4addr' must be a valid IPv4 address");
assert.equal(typeof (tcpPort), 'number',
    "argument 'tcpPort' must be a number");
assert.ok(!isNaN(tcpPort) && tcpPort > 0 && tcpPort < 65536,
    "argument 'tcpPort' must be a positive integer between 1 and 65535");
assert.equal(typeof (timeout), 'number',
    "argument 'timeout' must be a number");
assert.ok(!isNaN(timeout) && timeout > 0,
    "argument 'timeout' must be a positive integer");
assert.equal(typeof (callback), 'function');

/* do work */
}

那些事例在概念上很简单,但是来得了地方大家所谈论的一对提议:

  • 参数,类型以及其余一些束缚被清楚的文书档案化。

  • 那么些函数对于收受的参数是格外严苛的,并且会在赢得错误参数的时候抛出特别(程序员的失误)。

  • 想必出现的操作退步集合被记录了。通过不相同的”name“值能够分别分化的老大,而”errno“被用来获得系统错误的详细消息。

  • 万分被传送的法子也被记录了(通过退步时调用回调函数)。

  • 归来的不当有”remoteIp“和”remotePort“字段,那样用户就足以定义本人的荒唐了(比如,叁个HTTP客户端的端口号是含有的)。

  • 即使很分明,不过接连失利后的情景也被明晰的笔录了:全体被打开的套接字此时壹度被关闭。

这看起来像是给三个很不难了然的函数写了超过超过4分之四个人会写的的超长注释,但多数函数实际上未有这么不难驾驭。全体提议都应有被有取舍的选取,假诺工作很简单,你应该团结做出判断,可是切记:用10秒钟把猜度发生的记录下来可能以往会为你或别的人节省数个钟头。

总结

  • 读书了怎么差异操作战败,即那一个能够被推测的就算在正确的顺序里也无力回天幸免的不当(例如,不能连接到服务器);而先后的Bug则是程序员失误。

  • 操作战败能够被拍卖,也应该被拍卖。程序员的失误不能够被处理或可信地还原(本不应有那样做),尝试那样做只会让难题更难调节和测试。

  • 2个加以的函数,它处理格外的点子或许是联合(用throw格局)要么是异步的(用callback或然伊夫ntEmitter),不会相互兼有。用户能够在回调函数里处理错误,也足以应用 class=”Apple-converted-space”> try/catch破获很是,但是不能够壹起用。实际上,使用throw并且期望调用者使用 class=”Apple-converted-space”> try/catch class=”Apple-converted-space”> 是很罕见的,因为 class=”Apple-converted-space”> NodeJS里的协同函数日常不会生出运维失利(首要的不等是相仿于JSON.parse的用户输入验证函数)。

  • 在写新函数的时候,用文书档案清楚地记录函数预期的参数,蕴含它们的档次、是还是不是有别的约束(例如必须是卓有功用的IP地址),只怕会发出的合理性的操作退步(例如不可能解析主机名,连接服务器战败,全数的劳务器端错误),错误是怎么传递给调用者的(同步,用throw,还是异步,用 class=”Apple-converted-space”> callback class=”Apple-converted-space”> 和 class=”Apple-converted-space”> EventEmitter)。

  • 贫乏参数恐怕参数无效是程序员的失误,一旦产生一连应该抛出极度。函数的我认为的可承受的参数恐怕会有多少个金色地带,不过只要传递的是叁个文档里写明接收的参数以外的东西,那正是四个程序员失误。

  • 传送错误的时候用专业的 class=”Apple-converted-space”> Error class=”Apple-converted-space”> 类和它标准的属性。尽恐怕把额外的有用音信放在对应的品质里。假如有希望,用约定的属性名(如下)。

附录:Error 对象属性命名约定

强烈建议你在发生错误的时候用那些名字来维持和Node大旨以及Node插件的同1。这一个大部分不会和有些给定的11分对应,但是出现难点的时候,你应当包罗其余看起来有用的音讯,即从编制程序上也从自定义的荒谬消息上。【表】。

Property name Intended use
localHostname the local DNS hostname (e.g., that you’re accepting connections at)
localIp the local IP address (e.g., that you’re accepting connections at)
localPort the local TCP port (e.g., that you’re accepting connections at)
remoteHostname the DNS hostname of some other service (e.g., that you tried to connect to)
remoteIp the IP address of some other service (e.g., that you tried to connect to)
remotePort the port of some other service (e.g., that you tried to connect to)
path the name of a file, directory, or Unix Domain Socket (e.g., that you tried to open)
srcpath the name of a path used as a source (e.g., for a rename or copy)
dstpath the name of a path used as a destination (e.g., for a rename or copy)
hostname a DNS hostname (e.g., that you tried to resolve)
ip an IP address (e.g., that you tried to reverse-resolve)
propertyName an object property name, or an argument name (e.g., for a validation error)
propertyValue an object property value (e.g., for a validation error)
syscall the name of a system call that failed
errno the symbolic value of errno (e.g., "ENOENT"). Do not use this for errors that don’t actually set the C value of errno.Use "name" to distinguish between types of errors.

脚注

  1. 大千世界有的时候会那样写代码,他们想要在出现异步错误的时候调用callback 并把错误当做参数字传送递。他们一无所能地觉得在大团结的回调函数(传递给
    doSomeAsynchronousOperation 的函数)里throw
    一个不行,会被外边的catch代码块捕获。try/catch和异步函数不是如此工作的。回忆一下,异步函数的含义就在于被调用的时候myApiFunc函数已经回到了。那表示try代码块已经脱离了。那几个回调函数是由Node直接调用的,外面并不曾try的代码块。借使你用那些反模式,结果就是抛出11分的时候,程序崩溃了。

  2. 在JavaScript里,抛出二个不属于Error的参数从技术上是可行的,不过应当被制止。那样的结果使得到调用堆栈未有十分大希望,代码也无能为力检查name质量,或然别的任何能够评释何地有失水准的习性。

  3. 操作失败和程序员的失误这一概念早在NodeJS从前就早已存在存在了。不严加地对应者Java里的checked和unchecked非凡,即使操作退步被认为是心有余而力不足防止的,比如 OutOfMemeoryError,被归为uncheked非凡。在C语言里有相应的概念,普通非常处理和使用断言。维基百科上关于断言的的稿子也有关于如几时候用断言哪一天用普通的错误处理的近乎的演讲。

  4. 假使那看起来相当现实,那是因为大家在成品环境中境遇这么过那样的题材。那实在很可怕。

 

转载自:https://segmentfault.com/a/1190000002741935