
本文介绍如何在 JavaScript 中优雅处理 RESTful API 的业务级错误(如 400 响应),通过自定义错误类封装 errors 字段,使调用方能精准识别并响应表单校验失败等场景。
本文介绍如何在 javascript 中优雅处理 restful api 的业务级错误(如 400 响应),通过自定义错误类封装 `errors` 字段,使调用方能精准识别并响应表单校验失败等场景。
在使用 fetch 进行 API 请求时,一个常见误区是认为 HTTP 错误状态(如 400、500)会自动触发 Promise 拒绝——实际上,fetch 仅在网络异常或请求未发出时 reject;即使服务器返回 {"status": 400, "errors": {...}},Promise 仍会 resolve。因此,必须显式检查响应体中的业务状态码,并在失败时主动抛出结构化异常。
✅ 推荐方案:自定义错误类 + 显式状态判断
首先定义一个可携带额外上下文的错误类:
class ApiValidationError extends Error {
constructor(message, errors) {
super(message);
this.name = 'ApiValidationError';
this.errors = errors || {};
// 兼容旧版 Node.js(v12+ 可省略)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, ApiValidationError);
}
}
}该类不仅继承标准 Error 行为,还安全地挂载 errors 对象(如 { pin: ["The Driver PIN has already been taken."], status: ["The selected Status is invalid."] }),便于上层统一提取字段级错误提示。
? 在请求链中抛出并捕获
方案一:Promise 链式写法(兼容性好)
return fetch(url, options)
.then(response => response.json())
.then(data => {
// 注意:此处判断的是 API 返回的 data.status(业务状态码),非 HTTP 状态码
if (data.status && data.status >= 400) {
throw new ApiValidationError('API validation failed', data.errors);
}
return data; // 成功时返回原始 data
})
.catch(error => {
if (error instanceof ApiValidationError) {
console.warn('Business error:', error.errors);
// ✅ 此处可触发表单高亮、Toast 提示等 UI 反馈
handleFormErrors(error.errors);
} else if (error.name === 'TypeError') {
// 网络错误(如 CORS、离线)
console.error('Network error:', error.message);
} else {
// 其他未预期错误
console.error('Unexpected error:', error);
}
});方案二:推荐使用 async/await(更清晰、易调试)
async function updateResource(url, options) {
try {
const response = await fetch(url, options);
const data = await response.json();
// 关键:根据 API 设计判断失败(示例中 status 字段表示业务状态)
if (data.status && (data.status < 200 || data.status >= 400)) {
throw new ApiValidationError(data.title || 'Request failed', data.errors);
}
return data; // 成功返回完整响应体
} catch (error) {
if (error instanceof ApiValidationError) {
// ? 调用方直接访问 error.errors 获取字段级错误
throw error; // 或直接处理:showFieldErrors(error.errors);
}
// re-throw 非业务错误,交由全局错误边界处理
throw error;
}
}
// 使用示例
try {
const result = await updateResource('/api/drivers/123', { method: 'PATCH', ... });
console.log('Success:', result);
} catch (e) {
if (e instanceof ApiValidationError) {
// 精准处理:e.errors.pin[0] → "The Driver PIN has already been taken."
renderValidationErrors(e.errors);
}
}⚠️ 注意事项与最佳实践
-
HTTP 状态码 vs 业务状态码:本例中 data.status 是 API 自定义的业务码(如 "status": 400),并非 response.status。若 API 返回标准 HTTP 状态码,建议优先检查 response.ok 或 response.status:
if (!response.ok) { const data = await response.json(); throw new ApiValidationError(`${response.status} ${response.statusText}`, data.errors); } 错误类型区分:使用 instanceof 比字符串比较 e.name === 'ApiValidationError' 更可靠(避免原型链污染风险)。
错误对象序列化:ApiValidationError 实例在跨 iframe 或 postMessage 传递时会丢失原型方法,如需序列化,请重写 toJSON() 方法或仅传递纯数据。
-
TypeScript 用户:可为 ApiValidationError 添加接口声明,确保 errors 类型安全:
interface ApiErrors { } class ApiValidationError extends Error { errors: ApiErrors; constructor(message: string, errors: ApiErrors) { super(message); this.name = 'ApiValidationError'; this.errors = errors; } }
通过此模式,你将获得可预测、可测试、可扩展的错误处理流程——前端不再需要反复解析 data 结构判断成败,而是专注业务逻辑与用户体验。

















