先记住这个答案
未捕获异常意味着错误逃出了预期处理边界,执行可能在修改共享状态、管理锁或推进事务的中途停止。全局监听器只能观察异常,无法自动恢复这些业务不变量,因此官方不建议捕获后继续正常运行。应把它当作最后一道故障边界,做必要且尽量简单的同步诊断或收尾后结束进程,由独立管理器重启。只想记录而不改变默认崩溃行为时可使用 uncaughtExceptionMonitor;可预期的业务失败仍应在请求或任务边界内处理。
- 监听到异常不代表部分副作用已撤销
- 全局处理器不能替代请求或任务的错误边界
- Monitor 可以观察而不取消默认崩溃行为
先看异常中断了哪些不变量
假设内存队列先移除任务,再更新正在执行集合,中间抛错会留下两个集合都找不到任务的状态。全局日志即使完整输出,也不会知道每个数据结构应恢复到哪个时刻。
同样,远端副作用可能已经发生,本地却没记录结果。继续接请求会让后续逻辑基于不一致状态运行,因此应通过持久事务、幂等和恢复流程处理不确定性,而不是在全局回调里简单重新执行原函数。
只观察错误时保留默认失败语义
下面是必须在独立测试进程运行的故障示例。Monitor 记录异常来源后不安装吞掉异常的 uncaughtException 监听,进程仍应以非零状态退出,说明观测与恢复责任可以分开。
示例只写固定演示错误,真实系统应使用经过约束的同步诊断路径,避免输出敏感输入或执行复杂序列化。处理器自身也可能失败,所以不能把它设计成依赖多个网络服务的完整业务流程。
const { writeSync } = require('node:fs');
process.on('uncaughtExceptionMonitor', (error, origin) => {
writeSync(2, 'observed:' + origin + ':' + error.message + '\n');
});
setImmediate(() => { throw new Error('demo-fatal'); });运行 node fatal.cjs 时,标准错误包含 observed:uncaughtException:demo-fatal,随后仍有默认异常信息且退出码非零。该示例用于独立验证,不应放入正在提供服务的进程执行。
把恢复交给能在故障进程外工作的组件
外部管理器可以发现进程退出并重新启动实例,但还需限制连续重启频率,避免配置错误导致高频崩溃循环。启动时应重新校验依赖和恢复状态,再通过就绪检查接入流量。
平时则应在异步任务和请求入口落实错误处理,把可预期的失败转成明确结果。进程崩溃只是无法可靠恢复时的最后边界,并不能替代对原始缺陷的定位、回归测试与业务一致性设计。
容易答错的地方
- 全局捕获后打印错误并继续服务
- 监听器的返回不会撤销已发生的部分修改,也不会重新建立被破坏的资源关系。若继续运行,错误可能以更隐蔽的数据问题出现;应终止不可信实例并修复最初逃逸的处理边界。
- 在致命处理器里等待长串网络清理
- 异常后的运行状态和资源可能已经不可靠,网络等待又可能永不完成。应将最后诊断与必要同步收尾控制在很小范围,可靠数据提交和恢复应建立在正常执行路径及外部系统上。
面试官还会怎么问?
uncaughtExceptionMonitor 与 uncaughtException 一样吗?
Monitor 是观察入口,安装它不会像普通异常监听器那样改变默认崩溃行为。若同时存在其他捕获机制,最终行为仍取决于整个配置,因此验证时要检查所有监听和捕获回调。
是不是任何请求参数错误都要重启进程?
不是,可预期输入错误应在请求边界校验并返回明确失败,保持状态一致。这里讨论的是逃出预期边界、无法确认运行状态的异常,不应把正常业务校验失败升级为进程故障。
为什么必须由外部管理器重启?
故障进程自身的事件循环、状态或资源可能受损,不能把恢复可靠性建立在它继续正常工作上。独立管理器能观察退出并重建实例,但仍要配合退避、告警和启动就绪验证。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。