前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >nodejs中错误捕获的一些最佳实践

nodejs中错误捕获的一些最佳实践

作者头像
IMWeb前端团队
发布于 2018-01-08 09:40:18
发布于 2018-01-08 09:40:18
1.8K00
代码可运行
举报
文章被收录于专栏:IMWeb前端团队IMWeb前端团队
运行总次数:0
代码可运行

本文作者:IMWeb yisbug 原文出处:IMWeb社区 未经同意,禁止转载

本文内容大部分来自 https://www.joyent.com/node-js/production/design/errors ,原文比较长,感觉也有点啰嗦,所以根据个人理解猜测梳理出本文,如果有错误欢迎指出,谢谢!

很多人其实不是很重视错误处理,但对于构建一个健壮的nodejs应用,错误处理是非常重要的一件事情,希望本文可以给你一些启发。

先抛出几个问题:

  1. 应该用哪种方式暴露错误?throwcallback(err, result)Event Emitter或者其他方式?
  2. 如何假设函数的参数?是否应该检测类型正确?非null,IP,QQ号码?
  3. 函数参数不符合预期该怎么处理?
  4. 应该如何区分不同类型的错误?例如Bad RequestService Unavailable
  5. 应该如何提供有用的错误信息?
  6. 应该如何捕获错误?使用try/catch,还是domains或者其他方式?

一些基础知识

关于Errorthrowtry...catch的一些基础知识链接

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch

node.js v7.2.0 domainprocess https://nodejs.org/api/domain.html https://nodejs.org/api/process.html

verror模块: rich JavaScript errors https://github.com/joyent/node-verror

抛出错误的几种方式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var myEmitter = new MyEmitter();
doSomeAsynchronousOperation(function (err) {
      if (err) throw (err); // 直接throw
      if (err) callback(err); // 使用callback,nodejs中常见的异步处理方式
      myEmitter.emit('error', new Error('whoops!')); // error事件
});

捕获错误

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
try{
    var result = JSON.parse(str);
}catch(e){
    // 捕获错误
}

分类错误

一般来说,我们将错误简单的分为两种类型:操作错误、编码错误。

对于有经验的人来说,写代码的时候都会处理一些常见的操作错误,例如JSON.parse总是会和try...catch一起,例如网络故障、远程服务器返回500等。这些错误并非bug。

对于程序来说,另外一种错误属于编码错误,这是程序的bug,解决的方式应该是修改代码,避免发生。例如read property of "undefined"、调用一个异步函数但没有传入callback、函数参数预期是Object但是传了一个String等等。

人们在谈论错误时,总是将这两种错误混在一起,实际上这两种错误是完全不同的。例如File not found是一种操作错误,但这不能说明哪里出错了,这可能仅仅表示程序应该先创建文件。

有些时候,同一个问题可能会导致多种错误。例如nodejs应用因为一个变量undefined导致crash,这是编码错误,客户端则会接收到ECONNRESET错误,这属于操作错误,对于客户端来说应该可以预期到服务器的这个错误。

如何处理 操作错误
  • 对于明确的操作错误类型,直接处理掉。

例如尝试打开一个log文件可能会导致 ENOENT ,那么创建这个文件即可。

  • 对于预料之外你不知道如何处理的错误,比较好的方式是记录error并crash,传递合适的错误信息给客户端。
如何处理 代码错误

最好的方式是立即crash。

这种错误是程序的bug,一般来说写再多的代码也避免不了。因为在node应用中,我们一般会监控挂掉的进程并自动重启,所以立即crash是比较好的方式。

调试这类问题的最佳方式,是在捕获到uncaught exception的时候,记录相关信息。

总之记住,server的代码错误(bug)传递到client时会成为一个操作错误,例如server捕获到uncaught exception则返回一个500,客户端来处理这个操作错误。

如何传递错误?

首先,最重要的是文档,描述这个函数做了些什么,接收什么类型的参数返回什么,可能会触发什么错误。

一些基本原则:

  • 同步的函数里,使用throw。使用者使用try...catch即可捕获错误。
  • 异步函数里,更常用的方式是使用callback(err, result)的方式。
  • 在更复杂的场景里,可以返回一个EventEmitter对象,代替使用callback。使用者可以监听emitter对象的 error事件。 例如读取一个数据流,我们可能会同时使用 req.on('data')req.on('error')req.on('timeout')

所以,使用throw还是callbacksEventEmitter,取决于:

  • 该错误是操作错误还是编码错误?
  • 该函数是同步还是异步?

此外,

  • 不管是同步(使用throw)或者异步(使用callbackEventEmitter),只使用一种方式传递错误,避免同时使用两种方式。这样的话,使用者就只需要使用一种方式来捕获错误,例如try...catch或者callback,不需要考虑更多的场景。

下面用一个特例来说明这一点:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 异步函数,err是操作错误,使用callback传递
fs.stat('不存在的文件',function(err){}) 
// 异步函数,参数错误,会立即抛出异常
fs.stat(null,function(err){})

在上例的第二种情况,会立即返回TypeError: path must be a string or Buffer,也就是说内部使用了throw,这种情况是不是和上面提到的有矛盾?

其实并不是,第二种情况属于编码错误(fs.stat只接收路径作为参数但我们给了他一个null),并不是操作错误。编码错误永远不应该被处理。

所以在使用fs.stat的时,使用者仍然只需要处理callback传递的错误,不需要使用try...catch

错误的输入属于哪种情况?编码错误还是操作错误?

这一点取决于函数申明的可以允许的类型,以及你如何来解释它们:

  • 如果得到的参数和申明的类型(不一定是指数据类型,也可能是IP地址、QQ号等类型)不一致,那么属于编码错误(使用者应该使用符合要求的参数)
  • 如果得到的参数和申明的类型一致,但函数不能处理这种情况,那属于操作错误。

你必须决定限制类型的严格程度。

例如需要连接到一个服务器,函数接收一个ip地址作为参数,那么有几种做法:

  • 函数只接收ip地址格式的参数,如果不符合格式,则立即抛出异常。
  • 函数接收任意字符串参数,如果参数不是ip地址格式,则使用callback发出一个异步错误,提示无法连接该地址。

这两种做法决定了同样的输入会导致编码错误或操作错误。对于大多数功能,我们强烈建议更严格,因为更宽松的限制会更容易导致使用错误以及浪费时间。

什么时候使用domainprocess.on('uncaughtException') ?

操作错误一般都可以使用明确的机制来处理(根据具体的错误对应处理,使用try...catchcallbackEventEmitter等)。

domain和全局的异常捕获主要是为了发现和处理未预料到的编码错误。

编写functions的具体建议

  • 清楚function的功能

必须明确几点:期待的参数、参数类型、额外约束(IP地址、QQ号码等)

如果任意一点不匹配,则立即抛出throw异常。

此外,还应该有:使用方可以预料到的操作错误、如何捕获这些错误、返回值。

  • 所有的erorr都使用Error对象(或者基于Error类的扩展)

所有的error都应该提供namemessage属性,并且stack也应该准确可用。

  • 使用name属性来区分错误类型

例如RangeErrorTypeError。 不要为每种错误取个名字,例如定义InvalidHostnameErrorInvalidIpAddressError这种来表示具体的错误,对于这种错误可以统一用InvalidArgumentError表示错误类型,然后在详细描述里补充更多信息。

  • 增加解释错误细节的属性

例如无法连接到服务器,可以增加一个remoteIp 属性表示试图连接的ip。

  • 如果传递一个较低级别的错误,考虑重新包装错误。

如果函数调用顺序如下:funcA -> funcB -> funcC,funcC返回一个加载配置失败的错误,funcB连接服务器失败。

那么,在funcA中,更希望得到包含这2个错误的信息。所以在funcB中捕获到funcC的错误时,包装并传递这些错误是有价值的。

包装底层的错误信息时,尽可能保留原始的信息,除了名称name,但不要改写原始的error对象。

一个组合多个错误的示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

这里有一个库可以帮我们做这件事:

https://github.com/joyent/node-verror

总结

  • 区分错误类型,是可预见的还是不可避免的,是操作错误还是bug。
  • 操作错误应该被处理。编码错误不应该被处理(全局处理并记录)。
  • 一个函数可能产生的操作错误,只应该使用同步(throw)或者异步一种方式。一般来说,在nodejs中,同步函数导致的操作错误是比较少见的,使用try...catch会很少,常见的是用户输入验证如JSON、解析等。
  • 一个函数的参数、类型、预期错误、如何捕获都应该是明确的。
  • 缺少参数、参数无效都属于编码错误,应该直接抛出异常(throw)。
  • 使用标准的Error类和标准属性。使用独立的属性,添加尽可能多的附加信息,尽可能使用通用的属性名称。

例如一些常见的属性名称:

localHostname、localIp、localPort、remoteHostname、remoteIp、remotePort、path、srcpath、dstpath、hostname、ip、propertyName、propertyValue、syscall、errno

最后

  • 不要尝试用try...catch去捕获一个异步函数的错误,这样会什么也得不到。
  • 如果不是产生错误,不要使用throw
  • nodejs之前就已经有操作错误、编码错误的概念,参考这里 https://en.wikipedia.org/wiki/Assertion_(software_development)#Comparison_with_error_handling(https://en.wikipedia.org/wiki/Assertion_(software_development%29#Comparison_with_error_handling)
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
NodeJS知识点梳理-第一篇
或者我们直接运行一个我们本地的js也是一样的,直接node csdn_demo.js
何处锦绣不灰堆
2020/05/28
1.1K0
七天学会NodeJS——第一天
Node.js 是一个能够在服务器端运行JavaScript的开放源代码、跨平台JavaScript运行环境。Node.js采用Google开发的V8内核运行代码,使用事件驱动、非阻塞和异步输入输出模型等技术来提高性能,可优化应用程序的传输量和规模。这些技术通常用于数据密集的即时应用程序。
葡萄城控件
2019/08/14
7K0
NodeJS模块研究 - stream
构建复杂程序的时候,通常会将系统拆解成若干功能,这些功能的之间的接口遵循一定的规范,以实现组合连接,共同完成复杂任务。例如管道运算符 | 。
心谭博客
2020/04/21
9460
NodeJS错误处理最佳实践
NodeJS的错误处理让人痛苦,在很长的一段时间里,大量的错误被放任不管。但是要想建立一个健壮的Node.js程序就必须正确的处理这些错误,而且这并不难学。如果你实在没有耐心,那就直接绕过长篇大论跳到“总结”部分吧。
竹清
2018/08/31
1.6K0
NodeJS错误处理最佳实践
Node.js基础 23456:全局对象,回调函数,模块,事件,读写文件(同步,异步)
类似的,在浏览器中有window 全局变量在所有模块中都可使用。不需要引用等。 全局变量 如console,setTimeout,require()等
代码之风
2019/03/20
1.6K0
Node.js基础 23456:全局对象,回调函数,模块,事件,读写文件(同步,异步)
Node.js 中使用 fs 模块进行文件读写操作详解
在现代 Web 开发中,Node.js 以其独特的非阻塞 I/O 和事件驱动架构,已经成为服务器端开发的首选平台之一。而在 Node.js 的核心模块中,fs(文件系统)模块扮演着至关重要的角色。它提供了丰富的 API,使得开发者能够轻松地进行文件的读取、写入、追加、复制和删除等操作。本文将深入探讨 fs 模块的使用方法,通过详细的示例代码,帮助读者掌握在 Node.js 环境下进行文件操作的基本技能。
Front_Yue
2024/12/25
3590
Node.js 中使用 fs 模块进行文件读写操作详解
Node入门教程(10)第八章:Node 的事件处理
Node中大量运用了事件回调,所以Node对事件做了单独的封装。所有能触发事件的对象都是 EventEmitter 类的实例,所以上一篇我们提到的文件操作的可读流、可写流等都是继承了 EventEmitter。当然我们也可以自定义具有事件行为的自定义对象,仅需要对其继承即可。 继承EventEmitter node的events模块封装了EventEmitter类型,此类型里面封装了事件注册、触发等API。 // 引入events模块 const EventEmitter = require('events
老马
2018/04/16
8090
关于 JavaScript 错误处理的最完整指南(上半部)
我们的开发过程中并不总是一帆风顺。特别是在某些情况下,我们可能希望停止程序或在发生不良情况时通知用户。
前端小智@大迁世界
2020/09/17
1.7K0
Promise最佳实践
Promise的构造函数,以及被 then 调用执行的函数基本上都可以认为是在 try…catch 代码块中执行的,所以在这些代码中即使使用 throw ,程序本身也不会因为异常而终止。Promise的状态也不会发生改变。
IMWeb前端团队
2019/12/03
9941
Promise最佳实践
前端Node.js面试题
Node.js 是一个开源与跨平台的 JavaScript 运行时环境。在浏览器外运行 V8 JavaScript 引擎(Google Chrome 的内核),利用事件驱动、非阻塞和异步输入输出模型等技术提高性能。我们可以理解为:Node.js 就是一个服务器端的、非阻塞式I/O的、事件驱动的JavaScript运行环境。
xiangzhihong
2021/12/30
1.5K0
前端Node.js面试题
NodeJS模块研究 - events
Nodejs 使用了一个事件驱动、非阻塞 IO 的模型。events模块是事件驱动的核心模块。很多内置模块都继承了events.EventEmitter。
心谭博客
2020/04/21
9110
100天精通Golang(基础入门篇)——第23天:错误处理的艺术: Go语言实战指南
大家好,我是猫头虎!今天我们继续探索Go语言的奥秘,迎来了我们的第23天学习之旅。在这一天,我们将重点关注Go语言中的错误处理机制。在实际的工程项目中,通过程序错误信息快速定位问题是我们的期望,但我们又不希望错误处理代码显得冗余和啰嗦。Go语言通过函数返回值逐层向上抛出错误,与Java和C#的try...catch异常处理显著不同。这种设计理念鼓励工程师显式地检查错误,以避免忽略应处理的错误,从而确保代码的健壮性。🚀
猫头虎
2024/04/09
1550
100天精通Golang(基础入门篇)——第23天:错误处理的艺术: Go语言实战指南
NodeJs 事件循环-比官方翻译更全面
事件循环使Node.js可以通过将操作转移到系统内核中来执行非阻塞I/O操作(尽管JavaScript是单线程的)。
mrsuperli
2019/12/24
2.2K0
NodeJs 事件循环-比官方翻译更全面
【nodejs原理&源码赏析(7)】【译】Node.js中的事件循环,定时器和process.nextTick
事件循环是Node.js能够实现非阻塞I/O的基础,尽管JavaScript应用是单线程运行的,但是它可以将操作向下传递到系统内核去执行。
大史不说话
2019/06/19
1.2K0
【nodejs原理&源码赏析(7)】【译】Node.js中的事件循环,定时器和process.nextTick
JavaScript 错误处理大全【建议收藏】
在所有的这些情况下,我们作为程序员都会产生错误,或者让编程引擎为我们创建一些错误。
疯狂的技术宅
2020/09/01
6.4K0
JavaScript 错误处理大全【建议收藏】
关于NodeJS工作原理的五个误解
NodeJS 诞生于 2009 年,由于它使用了 JavaScript,在这些年里获得了非常广泛的流行。它是一个用于编写服务器端应用程序的 JavaScript 运行时,但是 "它就是JavaScript" 这句话并不是 100% 正确的。
ConardLi
2020/04/01
1.6K0
一些简单的错误处理函数(二)
接下来,我们继续学习 PHP 中的错误处理函数。上次学习过的函数是错误信息的获取、设置、发送等功能,今天学习的内容主要是关于错误的捕获相关的函数。
硬核项目经理
2020/06/30
6210
NodeJS模块研究 - fs
node 的fs文档密密麻麻的 api 非常多,毕竟全面支持对文件系统的操作。文档组织的很好,操作基本分为文件操作、目录操作、文件信息、流这个大方面,编程方式也支持同步、异步和 Promise。
心谭博客
2020/04/21
9440
这里有一份Node.js入门指南和实践,请注意查收
JS 是脚本语言,脚本语言都需要一个解析器才能运行。对于写在 HTML 页面里的 JS,浏览器充当了解析器的角色。而对于需要独立运行的 JS,NodeJS 就是一个解析器。
null仔
2020/02/28
3.6K0
这里有一份Node.js入门指南和实践,请注意查收
Node理论笔记:异步编程
在JavaScript中,函数是一等公民,使用非常自由,无论是调用它,或者作为参数,或者作为返回值均可。
Ashen
2020/06/01
1K0
推荐阅读
相关推荐
NodeJS知识点梳理-第一篇
更多 >
LV.1
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验