由于nodejs是非阻塞单进程单线程的,一旦nodejs抛出异常,整个服务就会停掉。服务将会非常不稳定。
错误异常有两种场景的出现,
针对这两种情况Nodejs都有默认的统一处理方式,就是给整个进程process对象监听相应的错误事件。
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
模拟异常
// 每秒钟打印一次时间,确保程序没有奔溃
(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 {
syncError()
} catch (e) {
/*处理异常*/
console.log(e.message)
}
console.log('异常被捕获了,我可以继续执行')
但是try catch方式无法处理异步代码块内出现的异常,你可以理解为执行catch时,异常还没有发生。
try {
asyncError()
} catch (e) {
/*异常无法被捕获,导致进程退出*/
console.log(e.message)
}
require("fs").mkdir('G://test', function (e) {
if (e) {
/*处理异常*/
console.log(e.message)
} else {
console.log('创建目录成功')
}
})
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('出错啦'));
new Promise((resolve, reject) => {
syncError()
})
.then(() => {
})
.catch((e) => {
/*处理异常*/
console.log(e.message)
})
或者
new Promise((resolve, reject) => {
try{
syncError()
}catch(e){
reject(e)
}
})
.then(() => {
})
.catch((e) => {
/*处理异常*/
console.log(e.message)
})
Promise同样无法处理异步代码块中抛出的异常
new Promise((resolve, reject) => {
asyncError()
})
.then(() => {
//...
})
.catch((e) => {
/*异常无法被捕获,导致进程退出*/
console.log(e.message)
})
Async是基于Promise的,可以用Await等待响应就可以用try/catch
来捕获了。
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.on('uncaughtException', function (e) {
/*处理异常*/
console.log(e.message)
});
asyncError()
syncError()
Promise未捕获
process.on('unhandledRejection', function (err, promise) {
console.error('有Promise没有被捕获的失败函数', err.message);
})
new Promise((resolve, reject) => {
syncError()
})
.then(() => {
//...
})
Promise中的异步错误
new Promise((resolve, reject) => {
asyncError()
})
.then(() => {
//...
})
process.on('uncaughtException', function (e) {
/*处理异常*/
console.log("uncaughtException:", e.message)
});
官方不推荐使用
https://nodejs.org/api/domain.html
https://nodejs.org/zh-cn/docs/guides/domain-postmortem/
domain模块,把处理多个不同的IO的操作作为一个组。注册事件和回调到domain,当发生一个错误事件或抛出一个错误时,domain对象会被通知,不会丢失上下文环境,也不导致程序错误立即退出,与process.on('uncaughtException')
不同。
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这个也可以处理任何类型异常的模块,显然是一个不错的选择。
let domain = require('domain')
let d = domain.create()
d.on('error', function (e) {
/*处理异常*/
console.log(e.message)
})
d.run(asyncError)
d.run(syncError)
或者
let d = require('domain').create();
d.on('error', function (err) {
console.log(err.stack);
d.exit();
});
asyncError();
d.enter();
express作为nodejs比较常用的框架,其实nodejs自己也有一定的异常错误捕获机制
// 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);