Node.js 全局未捕获异常处理
全局异常处理是错误未被捕获时的最后防线。
uncaughtException
捕获未处理同步异常
JavaScript
// 未被 try/catch 捕获的异常
process.on('uncaughtException', (err, origin) => {
console.error('未捕获异常:', err.message);
console.error('来源:', origin);
console.error('堆栈:', err.stack);
// 记录日志
logger.error('Uncaught Exception', err);
// 必须退出进程
process.exit(1);
});
触发场景
JavaScript
// 同步代码抛出异常未被捕获
function risky() {
throw new Error('崩溃');
}
risky(); // 无 try/catch,触发 uncaughtException
处理策略
JavaScript
process.on('uncaughtException', (err) => {
// 区分可恢复和不可恢复
if (err.isOperational) {
logger.warn('可恢复错误:', err.message);
// 可选择不退出,继续运行
} else {
logger.error('不可恢复错误:', err.stack);
// 不可恢复错误必须退出
process.exit(1);
}
});
unhandledRejection
捕获未处理 Promise 拒绝
JavaScript
// 未被 .catch() 或 try/catch 捕获的 Promise 错误
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理 Promise 拒绝:');
console.error('原因:', reason);
console.error('Promise:', promise);
// 记录日志
logger.error('Unhandled Rejection', reason);
});
触发场景
JavaScript
// Promise 没有 catch
Promise.reject('错误');
// 触发 unhandledRejection
// async 函数没有 try/catch
async function risky() {
throw new Error('崩溃');
}
risky(); // 无 try/catch,触发 unhandledRejection
处理策略
JavaScript
process.on('unhandledRejection', (reason) => {
if (reason instanceof Error) {
logger.error('未处理错误:', reason.stack);
} else {
logger.error('未处理拒绝:', reason);
}
// Node.js 15+ 默认退出
// 可选择记录后继续或退出
});
rejectionHandled
捕获延迟处理的拒绝
JavaScript
// Promise 拒绝后延迟添加 catch
process.on('rejectionHandled', (promise) => {
console.log('Promise 拒绝后被处理');
});
const p = Promise.reject('延迟处理');
// 此时触发 unhandledRejection
setTimeout(() => {
p.catch(err => console.log(err));
// 此时触发 rejectionHandled
}, 1000);
完整全局处理方案
标准配置
JavaScript
// 全局异常处理
process.on('uncaughtException', (err) => {
logger.error('Uncaught Exception', {
message: err.message,
stack: err.stack,
code: err.code
});
// 优雅关闭
gracefulShutdown(() => {
process.exit(1);
});
});
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled Rejection', {
reason: reason instanceof Error ? reason.stack : reason,
promise: promise
});
});
// Node.js 15+ 可设置严格模式
// process.on('unhandledRejection', (reason) => {
// throw reason; // 转为 uncaughtException
// });
优雅关闭
JavaScript
let isShuttingDown = false;
function gracefulShutdown(callback) {
if (isShuttingDown) return;
isShuttingDown = true;
console.log('开始优雅关闭...');
// 停止接收新请求
server.close();
// 等待现有请求完成
setTimeout(() => {
console.log('关闭完成');
callback();
}, 5000);
}
process.on('SIGTERM', () => gracefulShutdown(() => process.exit(0)));
process.on('SIGINT', () => gracefulShutdown(() => process.exit(0)));
使用 domain(已废弃)
JavaScript
// domain 模块已废弃,不建议使用
const domain = require('domain');
const d = domain.create();
d.on('error', (err) => {
console.error('Domain 捕获:', err);
});
d.run(() => {
riskyOperation();
});
PM2 与全局异常
JavaScript
// PM2 配合全局异常处理
process.on('uncaughtException', (err) => {
logger.error(err);
process.exit(1); // PM2 自动重启
});
// ecosystem.config.js
module.exports = {
apps: [{
name: 'app',
script: 'app.js',
autorestart: true,
max_restarts: 10, // 最大重启次数
restart_delay: 3000 // 重启延迟
}]
};
最佳实践
不依赖全局处理
JavaScript
// 错误:依赖全局处理
async function risky() {
throw new Error('错误');
}
risky(); // 等待全局捕获
// 正确:主动处理
async function risky() {
try {
throw new Error('错误');
} catch (err) {
logger.error(err);
}
}
// 或调用处处理
risky().catch(err => logger.error(err));
区分错误类型
JavaScript
class OperationalError extends Error {
constructor(message) {
super(message);
this.isOperational = true;
}
}
process.on('uncaughtException', (err) => {
if (err.isOperational) {
logger.warn('可恢复错误:', err.message);
} else {
logger.error('程序错误:', err.stack);
process.exit(1);
}
});
注意事项
uncaughtException后进程状态不稳定,应退出- Node.js 15+
unhandledRejection默认退出进程- 全局处理是最后防线,不是日常手段
- 必须记录错误日志便于排查
- 使用 PM2 自动重启恢复
要点总结
uncaughtException捕获未处理同步异常unhandledRejection捕获未处理 Promise 拒绝- 捕获后记录日志并优雅退出
- 使用
isOperational区分可恢复错误 - PM2 配合自动重启保证稳定
📝 发现内容有误?点击此处直接编辑