错误处理与 try/catch:真实项目里应该捕什么错?

前言

前端开发中,错误处理很常见:接口失败、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 内部的 rejectAjax 的失败回调等异步错误,也不能被外层的 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 的关系: 以前大家用 XMLHttpRequestjQuery$.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,否则手动 throw
  • res.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 的地方

  1. JSON 解析JSON.parseres.json() 等容易抛错的地方
  2. 可能抛出异常的第三方库:如日期解析、复杂计算等
  3. 同步的、可能出错的业务逻辑:如参数校验、数据转换等

5.2 不要指望 try/catch 的地方

  1. 异步回调 :用 Promise.catchasync/awaittry/catch 包住 await 那一行
  2. 事件监听 :在回调里单独加 try/catch
  3. 全局错误 :用 window.onerrorunhandledrejection 做兜底

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.parsetry/catch 建议封装 safeParse
业务异常 根据 code 分支,返回固定结构 给用户明确提示
系统异常 catch 后上报 + 通用提示 避免暴露内部错误细节
异步错误 Promise .catch / async try 不要指望外层同步 try 捕获

记住:不是所有错误都要用 try/catch,关键是区分"可预期业务失败"和"真正的异常",在合适的地方用合适的工具处理。


以上就是本次的学习分享,欢迎大家在评论区讨论指正,与大家共勉。

我是 Eugene,你的电子学友。

如果文章对你有帮助,别忘了点赞、收藏、加关注,你的认可是我持续输出的最大动力~

相关推荐
Amumu121382 小时前
CSS引入方式
前端·css
我是Superman丶2 小时前
【Demo】✋ 数字手势识别 Html
前端·html
HelloReader2 小时前
Leptos + Tauri 2 前端配置Trunk + SSG + 移动端热重载一次打通(Leptos 0.6 口径)
前端
HelloReader2 小时前
Next.js + Tauri 2 用 Static Export 把 React 元框架装进桌面/移动端
前端
Jason_Honey23 小时前
【蚂蚁金服Agent算法岗一面】
人工智能·算法·自然语言处理·面试
Wect3 小时前
从输入URL到页面显示的完整技术流程
前端·面试·浏览器
没有bug.的程序员3 小时前
自动化测试之魂:Selenium 与 TestNG 深度集成内核、Page Object 模型实战与 Web UI 交付质量指南
前端·自动化测试·selenium·ui·testng·page·object
夕除3 小时前
js--22
前端·javascript·python
南雨北斗4 小时前
TypeScript 配置文件 `tsconfig.json`
前端