• 首页
  • nodejs
  • node.js 使用domain模块捕获异步回调中的异常

node.js 使用domain模块捕获异步回调中的异常

和其他服务器端语言相比,貌似node.js 对于异常捕捉确实非常困难.

首先你会想到try/catch ,但是在使用过程中我们会发现并没有真正将错误控制在try/catch 语句中.

为什么? 

答案是这样的:

node.js 是异步IO执行,所以我们将try/catch 反正异步回调函数中,当出现一个异常的时候,我们try 拿到这个错误时应该错过了当前程序运行堆栈,

而在node.js原生的uncaughtException 处理事件却挂在 process 对象上,OMG,你可想而知,如果一个异常出现时,当前运行的 process 会直接挂掉,

导致错误永远不会走到 catch 语句.所以 try/catch 在异步回调中显得苍白无力,我们有什么办法控制这种异步回调的异常吗?

当然,我们下面来介绍一种比较有效的处理方法:

domain模块,这是node自带的一个模块,看名字确实很难联想到他是用来捕捉异常的,它能帮我们做一些try /catch无法做到的事情.


首先引用模块

var domain = require('domain');

如果你也使用Express框架,可以直接将domain 做成一个 Express 的中间件,这样所有的 request及response 中的异步错误都可以捕捉到.

app.configure(function () {
    app.use(function (req, res, next) {
        var reqDomain = domain.create();
        reqDomain.on('error', function (err) {  // 下面抛出的异常在这里被捕获,触发此事件
            console.log('捕获到错误');
            res.send(500, err.stack);           // 成功给用户返回了 500
        });
        reqDomain.run(next);
    });
});

上面这种写法是Express 的一个中间件写法, app.use(function(req,res,next){  .....}) 这是一个中间件,用来接收所有http请求,这里你可以捕获request 和 response 对象用来做一些过滤,逻辑判断等等,最后通过 next 来放行本次请求,那么这个中间件就完成了他的一次使命.

而我们为了让 domain 模块来接管所有的http请求中的异常,所以把它写成一个中间件是非常方便的.


然后我们在 process 上将未处理的异常捕捉一下,做到万无一失.

process.on('uncaughtException', function (err) {
    console.error("uncaughtException ERROR");
    if (typeof err === 'object') {
        if (err.message) {
            console.error('ERROR: ' + err.message)
        }
        if (err.stack) {
            console.error(err.stack);
        }
    } else {
        console.error('argument is not an object');
    }
});


//下面我们来定义几个路由,测试一下. 

app.get('/', function (req, res) {
    levelDB.getData('test', function (err, v) {
        //这里我从levelDB 中读取一条数据,需要引入 levelDB 模块.
        err ? res.send({status: "fail"}) : res.send({status: "success", value: v});
    });
})


//我们再想办法让程序出现一个异步异常

app.get('/err', function (req, res) {
    //throw new Error('exception'); 
    setTimeout(function () {
        throw new Error('exception'); // 抛出一个异步异常
    }, 1000);
})


//我们之所以想到用 setTimeout 就是想模拟一个异步的回调,如果你直接 throw new Error('exception');这样就不是异步了,直接会被 process 上的 uncaughtException 来接管.


通过上面定义的两个路由,我们来分别访问一下

这个路由我们从数据库读取了一条记录最后返回给客户端,中间没有出现任何异常.


这是我们模拟的一个异常已经被捕捉到,这是服务器端返回给客户端的一个500提示信息,这时我们再看一眼控制台,node 进程并没有挂掉.

每次 domian 捕获到错误后,我都在控制台输出了一行提示信息 "捕获到错误" 当前进程并没有因为异常而挂掉,这就是我们要的效果.

回到顶部