在 async/await 环境下处理自定义错误,核心思路其实很清晰:用 `throw` 抛出一个继承自 `Error` 的实例(或者至少是一个带有 `message` 和 `name` 属性的对象),然后在调用处用 `try/catch` 兜底捕获。这听起来简单,但实际项目中踩坑的点不少,下面展开聊聊几个关键环节。

开篇先明确一个基本原则:在 async/await 环境下自定义错误抛出,核心是用 `throw` 抛出一个继承自 `Error` 的实例(或至少具备 `message` 和 `name` 属性的对象),并在调用处用 `try/catch` 捕获处理。
定义可识别的自定义错误类
推荐继承原生 `Error` 类,这样堆栈信息、`instanceof` 判断和调试体验都能正常运作。具体做法是:构造函数中调用 `super(message)`,并设置 `this.name`;还可以添加额外属性(如 `code`、`status`)用于业务区分。有一点需要避免:只抛字符串或普通对象,那样后续很难统一拦截和做类型判断。
示例代码:
```javascript
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.code = 'VALIDATION_ERROR';
this.field = field;
}
}
// 使用
async function createUser(data) {
if (!data.email) {
throw new ValidationError('邮箱不能为空', 'email');
}
// ...
}
```
在 await 后主动检查并抛出自定义错误
async 函数本身不会自动将 rejected Promise 转为自定义错误,所以需要显式判断响应或结果,再决定是否抛出。常见场景是调用第三方 API 后,检查 `response.status` 或返回体中的 `error` 字段。这里的关键是:不要依赖 `catch` 原始错误,而是根据业务逻辑“翻译”成领域错误。另外,尽量避免直接 `throw err`,那样可能丢失上下文信息,建议包装后再抛。
示例代码:
```javascript
async function fetchUser(id) {
const res = await fetch(`/api/users/${id}`);
const data = await res.json();
if (!res.ok) {
throw new ApiError(`请求失败: ${data.message || res.statusText}`, {
status: res.status,
code: data.code
});
}
if (!data.user) {
throw new NotFoundError('用户不存在');
}
return data.user;
}
```
统一错误处理与中间层封装
在大型项目中,可以在请求函数或业务方法外层做标准化错误转换。比如封装一个 `safeAwait` 工具函数,自动将 Promise rejection 映射为自定义错误;或者在 API 客户端中统一拦截 HTTP 错误码,映射为 `NetworkError`、`AuthError` 等。这样做的好处是避免每个 `await` 都写重复的 `if (res.error)` 判断。
一个小技巧:用 `try/catch` 包裹单个 `await` 更清晰,不建议把多个 await 塞进一个 try 块里——否则一旦出错,很难准确定位是哪一步造成的。
在 catch 中精准识别和响应自定义错误
利用 `instanceof` 或 `error.name` 来区分错误类型,实现差异化处理。比如对 `ValidationError` 提示表单字段问题,对 `AuthError` 触发登出或跳转登录页,对未知错误兜底显示通用提示,并上报监控系统。
示例代码:
```javascript
try {
await submitForm(data);
} catch (err) {
if (err instanceof ValidationError) {
showFieldError(err.field, err.message);
} else if (err instanceof AuthError) {
redirectToLogin();
} else {
notifyUser('操作失败,请稍后重试');
reportError(err);
}
}
```
这套流程在实际项目中非常实用,既能保证错误信息的完整性,又能让业务层清晰地处理不同场景。如果你正在搭建一个中型以上的前端应用,不妨试试这种模式。