异常处理是程序运行中必须要关注的地方,当异常出现后,应该第一时间关注到,并且快速解决。大部分程序员们都不敢保证自己的代码百分比正确,所以应该在写代码时就要对异常提前做预防处理,尽量保证在异常出现时,给用户一个友好的提示,不至于服务挂起导致请求超时,并且能将异常信息做记录上报,方便后期排查解决。
同步代码的异常捕获处理
try{}catch
同步代码中的异常使用try{}catch结构即可捕获处理。
try {
throw new Error('错误信息');
} catch (e) {
console.error(e.message);
}
可以正常捕获到
异步代码的错误处理
在异步代码中就不能用try catch 了, try catch方式无法处理异步代码块内出现的异常,你可以理解为执行catch时,异常还没有发生。
try {
setTimeout(()=>{
throw new Error('错误信息');
})
} catch (e) {
console.error('error is:', e.message);
}
结果:没有捕获到异步错误。
使用回调函数 callback方式
不过大多数Node.js核心API都提供了回调函数来处理错误,比如:
const fs = require("fs");
fs.readFile("/null", (err, data) => {
if (err) console.log(err);
else console.log(data);
});
通过回调函数的err参数来检查是否出现错误,再进行处理。Node.js之所以采用这种错误处理机制,是因为异步方法所产生的方法并不能简单地通过try...catch机制进行拦截。
如果在程序执行过程中出现了未捕获的异常,那么程序就会崩溃,因此先把错误进行捕获后传递到回调函数中,就不会让程序奔溃,后续代码也能被顺利运行
event 方式 事件监听对象EventEmitter
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 方式
Promise无法处理异步代码块中抛出的异常
new Promise((resolve, reject) => {
syncError()
/* or
try{
syncError()
}catch(e){
reject(e)
}
*/
})
.then(() => {
//...
})
.catch((e) => {
/*处理异常*/
console.log(e.message)
})
使用process模块
process方式可以捕获任何异常(不管是同步代码块中的异常还是异步代码块中的异常)
process.on('uncaughtException',function(err){}) //监听未捕获的异常
process.on('unhandledRejection',function(err,promise){}) //监听Promise没有被捕获的失败函数
process上监听uncaughtException事件,可以捕获到整个进程包含异步中的错误信息,从而保证应用没有奔溃。
但是新的问题随之而来,因为异常不可预料的发生后,当异常出现时,直接从对应执行栈中断,而到process捕获的异常事件下,导致了v8引擎的垃圾回收功能不能按照正常流程工作,然后开始出现内存泄漏问题。
相对于异常来说,内存泄漏也是一个不能忽视的严重问题,而process.on('uncaughtException')的做法,很难去保证不造成内存的泄漏。所以当捕获到异常时,显式的手动杀掉进程,并开始重启node进程,即保证释放内存,又保证了保证服务后续正常可用。
process.on('uncaughtException', (e)=>{
console.error('process error is:', e.message);
process.exit(1);
restartServer(); // 重启服务
});
但是上面的做法有一点直接,大家不免存疑惑,如果单进程单实例的部署下,杀掉进程在重启这一段时间内服务不能正常可用怎么办?这显然是不合理的。 多进程先启动再杀进程。
domain方式 (之后版本已废弃)
const domain = require('domain');
const d = domain.create();
d.on('error', (err) => {
console.log('err', err.message);
//如果这是http请求, 那么是在这里返回404码
});
function excute() {
try {
setTimeout(()=>{
throw new Error('错误信息');
});
} catch (e) {
console.error('error is:', e.message);
}
};
d.run(excute);
domain简单介绍
domain
域。
简化了异步代码的异常处理,可以捕捉try catch无法捕捉的异常。
引入
const domain = require('domain')
1
domain模块把处理多个不同的IO操作为一个组。
当发生一个错误事件或抛出一个错误时,domain对象会被通知,不会丢失上下文环境,也不会导致程序错误立即退出、
显示绑定和隐式绑定
显示绑定:把不是在domain上下文中定义的变量,以代码的方式绑定到domain。
隐式绑定:把在domain上下文中定义的变量,自动绑定到domain对象。
属性
domain.members:已加入domain对象的域定时器和事件发射器的数组。
方法
domain.run(function):在域的上下文运行提供的函数,隐式的绑定了所有的事件分发器,计时器和底层请求。
doamin.add(emitter):显示的增加事件。
doamin.remove(emitter):删除事件。
domain.bind(callback):返回的函数是一个对于所提供的回调函数的包装函数。当调用这个返回的函数时,所有被抛出的错误都会被导向到这个域的error事件。
domain.intercept(callback):和bind类似,除了捕捉错误外,它还会拦截error对象作为参数传递到这个函数。
domain.enter():进入一个异步调用的上下文,绑定到domain。
domain.exit():退出当前的domain,切换到不同的链的异步调用的上下文中。
domain.dispose():释放一个domain对象,让node进程回收这部分资源。
domain.create():返回一个domain对象。