JS 代码技巧 vol.5 — 10 个错误处理套路,从 try/catch 到 Error Boundary

JS 代码技巧 vol.5 --- 10 个错误处理套路,从 try/catch 到 Error Boundary

小不的代码技巧系列第 5 期

主题:错误处理套路

这期话题有点重------错误处理这事,写好是"稳如老狗",写差是"半夜被告警"😈

哈喽哇!我是小不不简说的不~

作为一个在代码界"翻车"无数次的选手,我算是看明白了:坑这东西吧,要么不踩,踩就踩大的😂

今天这期,主打一个 错误处理套路------不整虚的,全是实战里哭出来的教训。看不看随你~反正翻车实录又不收钱

Ps:文末有惊喜(不是广告!)


1. async/await 时代:try/catch 统一收口

Promise 链时代大家都用 .catch,async/await 出来后,try/catch 是首选。

js 复制代码
// ❌ 反例:混用风格,心智负担重
fetchUser(id)
  .then((user) => render(user))
  .catch((e) => console.error(e));

// ✅ 正解:async/await + try/catch
async function loadUser(id) {
  try {
    const user = await fetchUser(id);
    render(user);
  } catch (e) {
    console.error("加载用户失败:", e);
  }
}

坑在哪 :async 函数里 throw 出去,调用方不 try/catch 也不 .catch,就是 unhandled rejection。Node 15+ 默认让进程退出,浏览器打 warning 还得靠 unhandledrejection 监听。


2. try/catch 不能跨 setTimeout 边界

经典踩坑:

js 复制代码
// ❌ 反例:以为能 catch 到
try {
  setTimeout(() => {
    throw new Error("boom");
  }, 100);
} catch (e) {
  console.error("抓到了"); // ❌ 根本抓不到
}

正解------错误必须自己处理:

js 复制代码
setTimeout(() => {
  try {
    doSomething();
  } catch (e) {
    console.error("抓到了", e); // ✓
  }
}, 100);

坑在哪 :定时器、Promise.then、事件回调里的 throw,都不会冒泡到外层 try/catch。每写一个异步回调都要想"我里面的错误会跑去哪"------这是异步错误处理的灵魂拷问。


3. 顶层 unhandledrejection 兜底:必须有

js 复制代码
// 浏览器
window.addEventListener("unhandledrejection", (e) => {
  console.error("未捕获的 Promise 拒绝:", e.reason);
  e.preventDefault(); // 阻止默认的 console 输出
  // 上报到 Sentry / 自家监控系统
});

// Node
process.on("unhandledRejection", (reason, promise) => {
  console.error("未捕获的 Promise 拒绝:", reason);
  // 业务上报
});

坑在哪 :光监听不上报等于没写。生产环境必须把 e.reason 里的 stack 抓到监控系统,不然线上炸了都不知道炸在哪。没有监控的错误处理都是自欺欺人


4. Error 子类化:自定义错误类型

业务里混用普通 Error 等于"错误全是一坨",定位起来血压升高。

js 复制代码
class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = "ValidationError";
    this.field = field;
  }
}

class NetworkError extends Error {
  constructor(message, status) {
    super(message);
    this.name = "NetworkError";
    this.status = status;
  }
}

// 用法
function saveUser(data) {
  if (!data.name) throw new ValidationError("用户名必填", "name");
}

坑在哪extends Error 在 TS 编译到 ES5 时有坑(target 调高就行)。new Error().name 记得改,否则全是 "Error" 一锅炖。Sentry 这类工具也是靠 name 字段分类的,不改等于裸奔。


5. 错误边界(Error Boundary):不让一个组件挂掉整个 app

类 React 框架都有 Error Boundary,Vue 里有 errorCaptured,原生 JS 自己写:

js 复制代码
// 简易版:保护一块 UI 不被单个组件错误带崩
function safeRender(fn, fallback) {
  try {
    return fn();
  } catch (e) {
    console.error("组件渲染失败:", e);
    return fallback;
  }
}

// 套娃用法
function App() {
  return (
    safeRender(
      () => Header(),
      () => "<div>Header 加载失败</div>",
    ) +
    safeRender(
      () => Main(),
      () => "<div>Main 加载失败</div>",
    )
  );
}

坑在哪 :错误边界只挡"同步 throw",异步错误(fetch、setTimeout)还是要靠 try/catch 自己处理。错误边界不是万能药,异步错误永远在边界外。


6. throw vs return Error:业务层选择

两种风格,看场景:

js 复制代码
// 风格 A:throw(异常流)
function getUser(id) {
  const user = db.find(id);
  if (!user) throw new NotFoundError("用户不存在");
  return user;
}

// 风格 B:return null/result(控制流)
function getUser(id) {
  const user = db.find(id);
  return user ?? null; // 调用方自己判断
}

坑在哪 :业务上"找不到用户"是异常还是正常?用 throw 当控制流是反模式 ------error stack 创建有成本,正常分支就别 throw。区分编程错误 (throw:传了 null 进来这种程序员失误)和业务错误(return:用户没找到这种正常情况)。


7. catch 里"吞"错误是万恶之源

js 复制代码
// ❌ 经典反模式
try {
  doSomething();
} catch (e) {
  // 啥也不干
}

至少要这样:

js 复制代码
try {
  doSomething();
} catch (e) {
  console.error(e); // 至少留个痕迹
  // 或者上报 Sentry
  // 或者把错误往上传给调用方
}

坑在哪 :"吞错"最怕的是默默积累。所有 catch 都得留下痕迹!所有 catch 都得留下痕迹!所有 catch 都得留下痕迹! ⚠️ 等用户报 bug 时啥日志都没有,才知道当初那个"懒得写 console.error"有多致命。


8. Error.captureStackTrace:自定义错误保留干净堆栈

V8 提供的 API,让你的自定义错误从"被抛出的地方"开始记录堆栈,而不是从构造函数:

js 复制代码
class BizError extends Error {
  constructor(message) {
    super(message);
    this.name = "BizError";
    Error.captureStackTrace(this, BizError); // 第二个参数:从这里开始截断
  }
}

throw new BizError("业务出错");
// 堆栈会跳过 BizError 构造函数那层,直接到 throw 行

坑在哪 :这是 V8 专属 API(Node 和 Chrome 都有),Firefox 没有------跨运行时小心。简单的项目直接 super(message) 也行,别为了漂亮堆栈费这个劲写给自己看 vs 写给团队看,自己拿捏


9. 进程级兜底:uncaughtException + unhandledRejection

Node 服务必装:

js 复制代码
process.on("uncaughtException", (err) => {
  console.error("未捕获异常:", err);
  // 上报
  // 视情况决定是否退出(推荐:退出让 pm2/systemd 重启)
  process.exit(1);
});

process.on("unhandledRejection", (reason) => {
  console.error("未捕获 Promise 拒绝:", reason);
  process.exit(1);
});

坑在哪 :这两个事件只用于兜底和上报别在这里写业务逻辑!别在这里写业务逻辑!别在这里写业务逻辑! ⚠️ 进程状态已经不可信,DB 连接可能断了,Redis 可能挂了一半,写啥都可能是错上加错。记录 → 上报 → 退出 → 让守护进程重启,是标准流程。


10. 可选链 + 空值合并 vs try/catch:防御式 vs 异常式

两种错误处理风格:

js 复制代码
// 防御式(Go 风格)
const name = user?.profile?.name ?? "匿名";
const age = user?.profile?.age ?? 0;
// 不抛错,靠默认值兜底

// 异常式(Java/Rust 风格)
try {
  const user = await fetchUser(id);
  render(user.profile.name);
} catch (e) {
  // 集中处理错误
}

坑在哪 :混用是大忌。函数 A 用 return null,函数 B 用 throw,调用方一脸懵。团队先定调

  • 轻业务(表单、UI 状态):防御式优先,少 try/catch
  • 重业务(支付、订单):异常式优先,关键路径必 try/catch + 上报

📦 收个尾

这 10 个技巧浓缩一下:

  • 最高频踩坑:try/catch 跨 setTimeout 边界、catch 里"吞"错、process 兜底写业务
  • 最值得收藏 :Error 子类化、unhandledrejection 监听、Error.captureStackTrace
  • 核心思路:区分编程错误(throw)和业务错误(return)

学到了就是赚到了,犹豫徘徊等于白来~


写到最后

想要啥技巧?评论区甩个题目过来~

  • 你刚踩的坑
  • 项目里反复写的代码
  • 想搞清楚但一直懒得查的 API

小不看到...不一定回 😂 毕竟代码里翻车太多,腾不出手~

Ps:三连随缘,催更的会被打 😂

相关推荐
wordpress资料库6 小时前
Next.js更适合移动开发 不适合web开发
开发语言·前端·javascript·next.js
卷无止境6 小时前
Quarkdown:赋予 Markdown 超能力的现代排版系统
前端·markdown
BerryS3N7 小时前
C++23新特性在CLion中的实战体验:从语法糖到生产力提升
前端·c++23
申君健24864182772027 小时前
# WebGPU 的渲染原理与 Shader
前端
随风123weber7 小时前
从前端响应式到后端背压:深度拆解生成式 UI(Generative UI)
前端
用户059540174467 小时前
用 Pytest 接管大模型记忆存储测试,状态一致性问题减少 90%
前端·css
IT_陈寒7 小时前
JavaScript的异步地狱里,我的Promise掉进了微任务陷阱
前端·人工智能·后端
嘟嘟07177 小时前
大前端工程师必学:用 BFF 架构搭建安全的 AI 流式应用
前端
meilindehuzi_a8 小时前
Vue 项目中的前端 BFF:跨域代理与流式请求实战
前端·javascript·vue.js