nodejs中错误捕获

2,568 阅读5分钟

nodejs中错误捕获

在捕获NodeJS错误前,我们首先得知道NodeJs的错误由哪些以及怎么抛出这些错误

NodeJS的错误类型

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

操作错误

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

处理操作错误

  1. 对于明确的操作错误类型,直接处理掉。
  2. 对于预料之外你不知道如何处理的错误,比较好的方式是记录error并crash,传递合适的错误信息给客户端。

编码错误

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

处理编码错误

最好的方式是立即crash。

NodeJS暴露错误的方式

一般通过throw、callback(err, result)错误回调函数、Event Emitter, domain暴露错误。

throw用法

throw new Error(name, message);

注意

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

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

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

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

示例

class InvalidArgumentError extends Error{
    constructor(message) {
        super();
        this.name = "InvalidArgumentError";
        this.message = message;
    }
}
throw new InvalidArgumentError("testError");
//控制台输出
Uncaught InvalidArgumentError: testError
    at <anonymous>:8:7

callback用法

一般用于异步函数对于错误处理的回调函数

callback(err, result)

EventEmitter用法

复杂场景中可以返回一个EventEmitter对象,代替使用callback。

events 模块只提供了一个对象: events.EventEmitter。EventEmitter 的核心就是事件触发与事件监听器功能的封装

//event.js 文件
var EventEmitter = require('events').EventEmitter; 
var event = new EventEmitter(); 
event.on('some_event', function() {  //事件注册
    console.log('some_event 事件触发'); 
}); 
setTimeout(function() { 
    event.emit('some_event');  //事件触发
}, 1000); 

domain

domain和全局的异常捕获主要是为了发现和处理未预料到的编码错误 Domain(域) 简化异步代码的异常处理,可以捕捉处理try catch无法捕捉的异常。

var domain = require("domain")

domain模块,把处理多个不同的IO的操作作为一个组。注册事件和回调到domain,当发生一个错误事件或抛出一个错误时,domain对象会被通知,不会丢失上下文环境,也不导致程序错误立即退出,与process.on('uncaughtException')不同。

Domain 模块可分为隐式绑定和显式绑定:

隐式绑定: 把在domain上下文中定义的变量,自动绑定到domain对象

显式绑定: 把不是在domain上下文中定义的变量,以代码的方式绑定到domain对象

示例

var EventEmitter = require("events").EventEmitter;
var domain = require("domain");

var emitter1 = new EventEmitter();

// 创建域
var domain1 = domain.create();
domain1.on('error', function(err){
   console.log("domain1 处理这个错误 ("+err.message+")");
});
// 显式绑定
domain1.add(emitter1);
emitter1.emit('error',new Error('通过监听器来处理')); // domain1 处理这个错误 (通过 domain1 处理)

var domain2 = domain.create();
domain2.on('error', function(err){
   console.log("domain2 处理这个错误 ("+err.message+")");
});
// 隐式绑定
domain2.run(function(){
   var emitter2 = new EventEmitter();
   emitter2.emit('error',new Error('通过 domain2 处理'));  // domain2 处理这个错误 (通过 domain2 处理)
});


domain1.remove(emitter1);
emitter1.emit('error', new Error('转换为异常,系统将崩溃!'));//转换为异常,系统将崩溃!

暴露错误的基本原则:

  1. 同步的函数里,使用throw。使用者使用try...catch即可捕获错误。

  2. 异步函数里,更常用的方式是使用callback(err, result)的方式。

  3. 在更复杂的场景里,可以返回一个EventEmitter对象,代替使用callback。使用者可以监听emitter对象的 error事件。 例如读取一个数据流,我们可能会同时使用 req.on('data')、req.on('error')、req.on('timeout') 。

  4. 一般用于发现和处理未预料到的编码错误。

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

NodeJS错误捕获的方式

  1. try catch 通常用于捕获throw抛出的错误

  2. callback(err, result)回调函数处理

  3. Emitter.on的回调函数 on注册了事件和回调函数, 并且在emit触发事件后会执行回调函数,以达到捕获错误的解决方式。

  4. process.on("error", function(err){}) 错误异常有两种场景的出现:

一种是代码运行中throw new error没有被捕获,另一种是Promise的失败回调函数,没有对应的reject回调函数处理,针对这两种情况Nodejs都有默认的统一处理方式,就是给整个进程process对象监听相应的错误事件。


process.on('uncaughtException',function(err){}) //监听未捕获的异常

process.on('unhandledRejection',function(err,promise){}) //监听Promise没有被捕获的失败函数
  1. express express作为nodejs比较常用的框架,其实nodejs自己也有一定的异常错误捕获机制
// Express' errorHandler
function errorHandler(err, req, res, next) {
console.error(err.stack);
}
app.use(errorHandler);

总结

  1. 区分错误类型,是可预见的还是不可避免的,是操作错误还是bug。

  2. 操作错误应该被处理。编码错误不应该被处理(全局处理并记录)。

3 一个函数可能产生的操作错误,只应该使用同步(throw)或者异步一种方式。一般来说,在nodejs中,同步函数导致的操作错误是比较少见的,使用try...catch会很少,常见的是用户输入验证如JSON、解析等。

  1. 一个函数的参数、类型、预期错误、如何捕获都应该是明确的。

  2. 缺少参数、参数无效都属于编码错误,应该直接抛出异常(throw)。

  3. 使用标准的Error类和标准属性。使用独立的属性,添加尽可能多的附加信息,尽可能使用通用的属性名称。

  4. 不要尝试用try...catch去捕获一个异步函数的错误,这样会什么也得不到。

8、如果不是产生错误,不要使用throw。