nodejs异步异常处理
nodejs 异步异常处理
环境:
nodejs : 4.4.4
express :4.1.13
pm2 :1.1.3
nodejs到目前为止已经发行的稳定版本是4.4.4,而用nodejs开发的系统的稳健性一直是被人所诟病的,现在我项目中就出现一有些运行错误或者异常,就导致了整个服务器退出的情况。
那nodejs可不可以像java一样,有一个统一的异常处理的handller,把所有的没有业务处理的异常抛出给统一的ExceptionHandler去处理呢?
注意,这里要处理的都是异步的业务异常,同步的异常处理不在这里讨论
同步异常处理:
//处理所有同步异常
app.use(function (err, req, res, next) {
// 带有四个参数的 middleware 专门用来处理异常
res.json("{'code':10000,'msg':'error'}");
});
怎么解决
目前其实还没有完美的方法去解决这个问题,但总是有替代的或者相似的解决方案:
- uncaughtException事件监听
- 使用domain模块捕捉异常
uncaughtException事件监听
uncaughtException 是一个非常古老的事件。当 Node 发现一个未捕获的异常时,会触发这个事件。并且如果这个事件存在回调函数,Node 就不会强制结束进程。
一般情况下,我们会将有可能出错的代码放到try/catch块里。但是到了Node.js,由于try/catch无法捕捉异步回调里的异常,Node.js原生提供uncaughtException事件挂到process对象上,用于捕获所有未处理的异常,以下是一个模似一个异步异常:
process.on('uncaughtException', function(err) {
console.error('Error caught in uncaughtException event:', err);
});
try {
process.nextTick(function() {
fs.readFile('non_existent.js', function(err, str) {
if(err) throw err;
else console.log(str);
});
});
} catch(e) {
console.error('Error caught by catch block:', e);
}
执行的结果是代码进到了uncaughtException的回调里而不是catch块。 uncaughtException虽然能够捕获异常,但是此时错误的上下文已经丢失,即使看到错误也不知道哪儿报的错,定位问题非常的不利。而且一旦uncaughtException事件触发,而且我们没有自定义重写process的uncaughtException挂载的话,整个node进程将crash掉。
NodeJS对于未捕获异常的默认处理是: - 触发 uncaughtException 事件 - 如果 uncaughtException 没有被监听,那么 - 打印异常的堆栈信息 - 触发进程的 exit 事件;
在app.js里监听了uncaughtException方法的话,进程是不会退出的,但是后面内存会一直飙升,比如,我请求了一个接口,里面模似了异步异常抛出:
app.get('/', function (req, res) {
setTimeout(function () {
throw new Error('async exception'); // 抛出一个异步异常
}, 1000);
});
执行完了以后,就会触发:uncaughtException方法,但不会立马退出,用pm2监控了他的内存使用情况,从19M到 50.309 MB的内存占用,估计随着时间的增加,内存会一直攀升。
使用domain模块捕捉异常
使用pm2来解决程序异步异常自动退出
当有异步异常导致服务进程自动退出时,pm2会自动重启应用程序,我请求了两次有异步异常的接口(会导致程序退出),每请求一次,完了后,在终端执行:pm2 show testExpress
,得到如下结果:
这是为什么?不要问我为什么,这是因为pm2 0.7.1 开始,对因为异步异常触发uncaughtException方法导致的程序退出作了优化,只要进程退出,就会通过pm2自动重启当前程序,如果你用了nodejs的cluster自带的负载均衡,则会重启当前分配的服务。
所以,正确的对uncaughtException的处理是:
process.on('uncaughtException', function(err) {
try {
//logger writer with err
var killTimer = setTimeout(function () {
process.exit(1);
}, 30000);
killTimer.unref();
server.close();
// if (cluster.worker) {
// cluster.worker.disconnect();
// }
} catch (e) {
console.log('error when exit', e.stack);
}
});
那配合pm2一起使用,绝对神作。。。。
对于一般的异步异常,不会导致程序退出的业务异常,则用domain来解决:
比如这个异步异常:
app.get('/', function (req, res) {
// if(!req.params.name)
// throw new Error('sync exception'); // 抛出一个同步异常
//
setTimeout(function () {
throw new Error('async exception'); // 抛出一个异步异常
}, 1000);
});
这个自定义的error异常,不会导致程序退出,也就不会调用uncaughtException方法。
// 使用 domain 来捕获大部分异常
app.use(function (req, res, next) {
var reqDomain = domain.create();
reqDomain.on('error', function (err) { // 下面抛出的异常在这里被捕获,只有异步异常
res.send("{'code':10000,'msg':'error'}"); // 成功给用户返回了 500
try {
// 强制退出机制
var killTimer = setTimeout(function () {
process.exit(1);
}, 30000);
killTimer.unref(); // 非常重要
// 自动退出机制,停止接收新链接,等待当前已建立连接的关闭
server.close(function () {
// 此时所有连接均已关闭,此时 Node 会自动退出,不需要再调用
process.exit(1); //来结束进程
});
} catch(e) {
console.log('err', e.stack);
}
});
reqDomain.run(next);
});