NodeJS的异常捕获

正文

由于nodejs是非阻塞单进程单线程的,一旦nodejs抛出异常,整个服务就会停掉。服务将会非常不稳定。

错误异常有两种场景的出现,

  • 一种是代码运行中throw new error没有被捕获

  • 另一种是Promise的失败回调函数,没有对应的reject回调函数处理

针对这两种情况Nodejs都有默认的统一处理方式,就是给整个进程process对象监听相应的错误事件。

1
2
3
4
5
6
7
process.on('uncaughtException',function(err){
console.error('未捕获的异常', err.message);
})

process.on('unhandledRejection', function (err, promise) {
console.error('有Promise没有被捕获的失败函数', err.message);
})

在有可能出现异常的地方,全部使用try { } catch(){ }进行嵌套。

注意

一般情况下,我们会将有可能出错的代码放到 try/catch 块里。

但是到了 Node.js,由于 try/catch 无法捕捉异步回调里的异常,Node.js 原生提供 uncaughtException 事件挂到 process 对象上,用于捕获所有未处理的异常。

处理异常的方式

总的来说处理异常有两种方式

  • 同步异常用try/catch
  • 异步异常要用各自的处理方式

模拟异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 每秒钟打印一次时间,确保程序没有奔溃
(function loop() {
console.log(new Date().getTime())
setTimeout(function () {
loop()
}, 1000)
})()
// 模拟同步代码块内出现异常
let syncError = () => {
throw new Error('Sync Error')
}
// 模拟异步代码块内出现异常
let asyncError = () => {
setTimeout(function () {
throw new Error('Async Error')
}, 100)
}

同步异常

try catch 方式

1
2
3
4
5
6
7
try {
syncError()
} catch (e) {
/*处理异常*/
console.log(e.message)
}
console.log('异常被捕获了,我可以继续执行')

但是try catch方式无法处理异步代码块内出现的异常,你可以理解为执行catch时,异常还没有发生。

1
2
3
4
5
6
try {
asyncError()
} catch (e) {
/*异常无法被捕获,导致进程退出*/
console.log(e.message)
}

异步异常

callback方式

1
2
3
4
5
6
7
8
require("fs").mkdir('G://test', function (e) {
if (e) {
/*处理异常*/
console.log(e.message)
} else {
console.log('创建目录成功')
}
})

event方式

1
2
3
4
5
6
7
8
9
10
let events = require("events");
//创建一个事件监听对象
let emitter = new events.EventEmitter();
//监听error事件
emitter.addListener("error", function (e) {
/*处理异常*/
console.log(e.message)
});
//触发error事件
emitter.emit("error", new Error('出错啦'));

Promise 方式

1
2
3
4
5
6
7
8
9
10
new Promise((resolve, reject) => {
syncError()
})
.then(() => {

})
.catch((e) => {
/*处理异常*/
console.log(e.message)
})

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
new Promise((resolve, reject) => {
try{
syncError()
}catch(e){
reject(e)
}
})
.then(() => {
})
.catch((e) => {
/*处理异常*/
console.log(e.message)
})

Promise同样无法处理异步代码块中抛出的异常

1
2
3
4
5
6
7
8
9
10
new Promise((resolve, reject) => {
asyncError()
})
.then(() => {
//...
})
.catch((e) => {
/*异常无法被捕获,导致进程退出*/
console.log(e.message)
})

Async/Await 方式

Async是基于Promise的,可以用Await等待响应就可以用try/catch来捕获了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var sleep = function (time) {
return new Promise(function (resolve, reject) {
syncError()
})
};

(async function () {
try {
await sleep(100);
} catch (e) {
/*处理异常*/
console.log(e.message)
}
})()

所有异常

process方式

process方式可以捕获任何异常(不管是同步代码块中的异常还是异步代码块中的异常)

1
2
3
4
5
6
7
process.on('uncaughtException', function (e) {
/*处理异常*/
console.log(e.message)
});

asyncError()
syncError()

Promise未捕获

1
2
3
4
5
6
7
8
9
10
process.on('unhandledRejection', function (err, promise) {
console.error('有Promise没有被捕获的失败函数', err.message);
})

new Promise((resolve, reject) => {
syncError()
})
.then(() => {
//...
})

Promise中的异步错误

1
2
3
4
5
6
7
8
9
10
11
12
new Promise((resolve, reject) => {
asyncError()
})
.then(() => {
//...
})


process.on('uncaughtException', function (e) {
/*处理异常*/
console.log("uncaughtException:", e.message)
});

domain方式

官方不推荐使用

https://nodejs.org/api/domain.html

https://nodejs.org/zh-cn/docs/guides/domain-postmortem/

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

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

  • 隐式绑定: 把在domain上下文中定义的变量,自动绑定到domain对象
  • 显式绑定: 把不是在domain上下文中定义的变量,以代码的方式绑定到domain对象
方法 & 描述
1 domain.create()
返回一个domain对象。
2 domain.run(function)
在域的上下文运行提供的函数,隐式的绑定了所有的事件分发器,计时器和底层请求。
3 domain.add(emitter)
显式的增加事件
4 domain.remove(emitter)
删除事件。
5 domain.bind(callback)
返回的函数是一个对于所提供的回调函数的包装函数。当调用这个返回的函数时,所有被抛出的错误都会被导向到这个域的 error 事件。
6 domain.intercept(callback)
和 domain.bind(callback) 类似。除了捕捉被抛出的错误外,它还会拦截 Error 对象作为参数传递到这个函数。
7 domain.enter()
进入一个异步调用的上下文,绑定到domain。
8 domain.exit()
退出当前的domain,切换到不同的链的异步调用的上下文中。对应domain.enter()。
9 domain.on(‘error’,function(err){})
捕获的错误监听

process方式虽然可以捕获任何类型的异常,但是process太过笨重,除了记录下错误信息,其他地方不适合使用,domain这个也可以处理任何类型异常的模块,显然是一个不错的选择。

1
2
3
4
5
6
7
8
let domain = require('domain')
let d = domain.create()
d.on('error', function (e) {
/*处理异常*/
console.log(e.message)
})
d.run(asyncError)
d.run(syncError)

或者

1
2
3
4
5
6
7
let d = require('domain').create();
d.on('error', function (err) {
console.log(err.stack);
d.exit();
});
asyncError();
d.enter();

express框架

express作为nodejs比较常用的框架,其实nodejs自己也有一定的异常错误捕获机制

1
2
3
4
5
6
7
8
9
// Express errorHandler
function errorHandler(err, req, res, next) {
console.error(err.stack);
res.status(500).json({
code: 1,
err: err.message || '500'
})
}
app.use(errorHandler);