前言
前端开发中,错误处理很常见:接口失败、JSON 解析报错、业务失败和系统异常混在一起处理。不少人要么到处 try/catch,要么该用的地方没用,导致线上问题难排查、用户提示不友好。
用原生 try/catch 配合 Promise/async 的错误处理 就能覆盖大部分场景。本文结合真实项目中的常见需求,说明什么时候用 try/catch、什么时候用 .catch()、业务异常和系统异常如何区分,只讲日常开发里 80% 会用到的情况。
适合读者:
- 会写 JS,但对
try/catch能抓什么、不能抓什么不太清晰 - 刚学 JS,希望一开始就掌握正确的错误处理方式
- 有经验的前端,想统一团队的异常处理和错误上报规范
一、先搞清楚:try/catch 到底能抓到啥
很多人的第一反应是:try/catch 能把"所有报错"都包住。实际上不是。
1.1 能抓到的:同步代码里的异常
javascript
try {
const obj = JSON.parse('{ invalid json }'); // 抛出 SyntaxError
console.log(obj);
} catch (e) {
console.error('解析失败:', e.message); // 能抓到
}
只要错误是在同步代码里抛出的 ,就会被 catch 捕获。
1.2 抓不到的:异步里的错误
javascript
try {
setTimeout(() => {
throw new Error('异步报错'); // 这个 catch 抓不到!
}, 0);
} catch (e) {
console.error(e); // 不会执行
}
setTimeout 的回调在下一个事件循环执行,执行时 try 早就结束了,所以这里的 catch 完全接不到这个错误。
同理,Promise 内部的 reject、Ajax 的失败回调等异步错误,也不能被外层的 try/catch 直接捕获,需要用别的方式处理。
二、Ajax 错误:别只盯着 try/catch
2.1 fetch 是什么?小白必读
在讲 Ajax 错误之前,先简单说一下 fetch 是什么,这样后面的代码你才能看得懂。
一句话: fetch 是浏览器自带的、用来向服务器发请求、拿数据的 API。
可以把它理解成:
- 你:在浏览器里打开页面,想拿到用户列表、商品信息等
- 服务器:数据放在后端,需要通过「请求」才能给你
- fetch:就是你发请求、等回复的「工具」
最基础的用法:
javascript
// 向 /api/user/list 这个地址发一个「我要数据」的请求
const res = await fetch('/api/user/list');
const data = await res.json(); // 把服务器返回的文本解析成 JSON 对象
console.log(data); // 拿到数据了
几个关键点:
| 概念 | 大白话解释 |
|---|---|
fetch(地址) |
向这个地址发请求,默认是 GET(取数据) |
| 返回值 | 是一个 Promise ,所以要用 await 等它完成 |
res |
服务器返回的「响应对象」,里面有状态码、响应体等 |
res.json() |
把响应体当成 JSON 解析,返回一个 JS 对象 |
res.text() |
把响应体当成普通文本,返回字符串 |
和 Ajax 的关系: 以前大家用 XMLHttpRequest 或 jQuery 的 $.ajax 来发请求,fetch 是浏览器后来提供的、更简单的新方式,本质做的都是同一件事:从浏览器向服务器发请求、拿数据。
搞懂这些后,下面就能理解为什么 fetch 的错误处理和你想的不太一样了。
2.2 常见误解
有人会这样写:
javascript
try {
const res = await fetch('/api/user/list');
const data = await res.json();
return data;
} catch (e) {
console.error('请求失败', e);
}
这里有个关键点:HTTP 4xx/5xx 并不会让 fetch 抛出异常,只有网络失败、跨域等才会抛。所以:
- 404、500 等错误 → 不会被
catch捕获 - 网络断开、超时 → 会被捕获
2.3 正确做法
要同时处理"网络/请求异常"和"HTTP 状态异常":
javascript
async function fetchUserList() {
try {
const res = await fetch('/api/user/list');
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const data = await res.json();
return data;
} catch (e) {
if (e.name === 'TypeError' && e.message.includes('fetch')) {
console.error('网络异常,请检查网络');
} else {
console.error('请求失败:', e.message);
}
throw e; // 让调用方也知道失败了
}
}
要点:
res.ok判断 2xx,否则手动throwres.json()可能抛 JSON 解析错误,会被catch捕获- 网络类错误在
catch里单独分支处理
三、JSON 解析错误:最容易漏掉的一类
3.1 常见场景
后端返回的是字符串,或者格式不对:
javascript
// 后端返回: "用户不存在"(纯字符串)
// 或者返回: {data: invalid}(非法 JSON)
const data = JSON.parse(response); // 直接崩
JSON.parse 抛的是 SyntaxError,如果不包一层 try/catch,会直接导致整段脚本报错,甚至影响后续逻辑。
3.2 推荐写法
javascript
function safeParse(str, fallback = null) {
try {
return JSON.parse(str);
} catch (e) {
console.warn('JSON 解析失败:', e.message, '原始内容:', str?.slice(0, 50));
return fallback;
}
}
const data = safeParse(response, {});
思路:
- 解析失败时返回一个兜底值(如
{}或[]),而不是让程序直接崩溃 - 打
warn方便排查问题 - 把解析逻辑封装成
safeParse,减少重复代码
四、业务异常 vs 系统异常:分类处理
很多人把所有错误都当成"失败"来处理,不做区分,交互和排查都会受影响。
4.1 业务异常(可预期的"业务失败")
- 比如:余额不足、未登录、参数错误、权限不足等
- 通常由后端通过 HTTP 状态码 + 业务码 + 消息返回
- 需要展示给用户,并做相应业务流程处理
4.2 系统异常(程序/环境错误)
- 比如:网络断开、服务器 500、JSON 解析失败、未捕获的运行时错误
- 多半需要上报、告警,用户只看到通用错误提示
4.3 实战示例
javascript
async function placeOrder(orderData) {
try {
const res = await fetch('/api/order/create', {
method: 'POST',
body: JSON.stringify(orderData),
});
const text = await res.text();
const data = safeParse(text, null);
if (!res.ok) {
// 业务异常:有明确的业务码和提示
if (data?.code === 'BALANCE_INSUFFICIENT') {
return { success: false, type: 'business', message: '余额不足,请先充值' };
}
if (data?.code === 'UNAUTHORIZED') {
return { success: false, type: 'auth', message: '请先登录' };
}
// 其他 4xx/5xx
return { success: false, type: 'system', message: data?.message || '服务器异常,请稍后重试' };
}
return { success: true, data };
} catch (e) {
// 系统异常:网络错误、JSON 解析异常等
reportError(e); // 上报监控
return { success: false, type: 'system', message: '网络异常,请检查网络后重试' };
}
}
调用方可以这样区分:
javascript
const result = await placeOrder(formData);
if (result.success) {
// 跳转支付/成功页
} else if (result.type === 'business' || result.type === 'auth') {
message.warning(result.message); // 业务提示,用户可操作
} else {
message.error(result.message); // 系统异常,建议稍后重试
}
五、实战中的几条规范
5.1 该用 try/catch 的地方
- JSON 解析 :
JSON.parse、res.json()等容易抛错的地方 - 可能抛出异常的第三方库:如日期解析、复杂计算等
- 同步的、可能出错的业务逻辑:如参数校验、数据转换等
5.2 不要指望 try/catch 的地方
- 异步回调 :用
Promise的.catch、async/await的try/catch包住await那一行 - 事件监听 :在回调里单独加
try/catch - 全局错误 :用
window.onerror或unhandledrejection做兜底
5.3 一个完整的小案例
javascript
async function getProductDetail(id) {
try {
const res = await fetch(`/api/product/${id}`);
const text = await res.text();
const data = safeParse(text);
if (!res.ok) {
if (data?.code === 'NOT_FOUND') {
return { ok: false, reason: 'product_not_found' };
}
throw new Error(data?.message || `请求失败: ${res.status}`);
}
return { ok: true, data };
} catch (e) {
if (e.name === 'SyntaxError') {
reportError(e, { context: 'JSON解析', id });
return { ok: false, reason: 'parse_error' };
}
if (e.message?.includes('fetch') || e.message?.includes('Network')) {
return { ok: false, reason: 'network_error' };
}
throw e;
}
}
六、总结
| 错误类型 | 处理方式 | 注意点 |
|---|---|---|
Ajax 网络错误 |
try/catch + res.ok 判断 |
4xx/5xx 不会自动抛异常 |
JSON 解析错误 |
对 JSON.parse 包 try/catch |
建议封装 safeParse |
| 业务异常 | 根据 code 分支,返回固定结构 |
给用户明确提示 |
| 系统异常 | catch 后上报 + 通用提示 |
避免暴露内部错误细节 |
| 异步错误 | Promise .catch / async try |
不要指望外层同步 try 捕获 |
记住:不是所有错误都要用 try/catch,关键是区分"可预期业务失败"和"真正的异常",在合适的地方用合适的工具处理。
以上就是本次的学习分享,欢迎大家在评论区讨论指正,与大家共勉。
我是 Eugene,你的电子学友。
如果文章对你有帮助,别忘了点赞、收藏、加关注,你的认可是我持续输出的最大动力~