JavaScript 中的错误处理:try/catch 与进阶技巧:高级篇
错误处理不仅仅是防止应用程序崩溃,它是构建可靠且容错的 JavaScript 和 Node.js 系统的基础方面。每个开发者都熟悉 try/catch 的基本用法,但在实际项目中,尤其是在服务端,会出现许多细微之处:异步流程、Promise 链、全局故障以及结构化自定义错误的需求。在本文中,我们将探讨高级错误处理技术,这些技术将帮助你在 Node.js 中编写更清晰、更可预测的代码。
1. 深入理解 try/catch 与异步性
经典的 try/catch 是同步的。它在捕获常规函数内部的错误时效果很好,但在处理异步回调或没有 await 的 Promise 时会失败。在 Node.js 中,90% 的代码都是异步的,这常常导致异常被“吞没”。
错误示例:try/catch 无法捕获 setTimeout 中的错误
function dangerousAsync() { setTimeout(() => { throw new Error('定时器故障!'); }, 1000);}
try { dangerousAsync();} catch (e) { console.log('已捕获:', e.message); // 这段代码将不会执行}在这个例子中,错误是在 try 上下文之外抛出的,Node.js 进程将会崩溃。正确的方法是使用 Promise 或 async/await。
解决方案:将异步代码包装在 Promise 中
function safeAsync() { return new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('定时器故障!')); }, 1000); });}
(async () => { try { await safeAsync(); } catch (e) { console.log('已捕获:', e.message); // 1 秒后将会执行 }})();主要规则:将任何异步操作包装在 promise 中,并在 try/catch 内部使用 await。这可确保错误在正确的上下文中被捕获。
2. 自定义错误类:语义与上下文
与其抛出 new Error('出了点问题'),不如创建你自己的错误类层次结构。这能让处理程序理解失败的类型并做出相应的响应(例如,记录日志、发送到监控系统或向用户返回特定的 HTTP 状态)。
class AppError extends Error { constructor(message, statusCode = 500) { super(message); this.name = this.constructor.name; this.statusCode = statusCode; Error.captureStackTrace(this, this.constructor); }}
class ValidationError extends AppError { constructor(message) { super(message, 400); }}
class DatabaseError extends AppError { constructor(message, originalError) { super(message, 503); this.originalError = originalError; // 保留原始错误用于调试 }}
// 用法function findUser(id) { if (!id) throw new ValidationError('用户 ID 是必填项'); // ... 可能抛出 DatabaseError 的逻辑}这种方法的优点:
- 可读性:类名能立即指示问题的性质。
- 路由处理:在全局处理器中,你可以检查
instanceof并选择响应策略。 - 附加字段:你可以添加任何数据(
statusCode、originalError、userId等)。
3. Node.js 中的全局错误处理器
即使有最仔细的控制,未处理的错误也可能泄露出来。在 Node.js 中,有两个关键事件用于捕获它们:uncaughtException 和 unhandledRejection。