再见 try/catch,我有更好的选择了!
前端异步代码里的错误处理,大多数人第一反应还是 try/catch。它能解决问题,但写多了难免觉得啰嗦:嵌套多、逻辑被打断,读起来也不太舒服。
所以这几年,越来越多项目开始尝试别的方式——写法更轻,结构更清晰。
这篇文章就来聊聊三种替代方案:一个语言层的提案,一个可以自己封装的小工具,还有一个现成的社区库。选好用的方式,能让错误处理简单不少。
传统 try/catch 的痛点
写 JavaScript 的人都知道,try/catch 虽然是处理异步错误的“正道”,但一旦多起来,整个代码就开始变得臃肿、重复、难读。
你可能见过这种写法:
try {
const data = await fetchUser();
doSomething(data);
} catch (e) {
console.error('出错了', e);
}
写一两个还好,但如果你有十几个异步调用,每个都要包一层 try/catch,不仅烦,而且破坏代码结构。不少人甚至为了偷懒,直接不处理错误或者一把包住:
try {
// 一大堆 await
} catch (e) {
// 一个错误搞不清是哪里来的
}
有没有更好的写法?有,而且不止一种。
语言层面的尝试:try 操作符提案
一个值得关注的思路来自一个全新的语言提案,它设想在 JavaScript 中引入一种新的 try 表达式语法,它不是语句,而是一个表达式。
提案地址:https://github.com/arthurfiorette/proposal-try-operator
const [ok, err, result] = try await fetchUser();
这个写法的意思很明确:
如果成功,ok 是 true,result 有值; 如果失败,ok 是 false,err 是错误对象。
这样一来,不仅避免了冗长的 try/catch,还天然具备结构化的错误处理方式。
const [ok, err, user] = await safeAwait(fetchUser());
if (!ok) {
console.error('请求失败:', err);
return;
}
console.log('用户数据:', user);
是不是很像 Go 的 val, err := fn(),或者 Rust 的 Result?这就是提案的核心:让错误处理从控制流转向值表达式。
虽然这个提案还在 Stage 1,离真正进入 JavaScript 还有一段距离,但它提出了一种很有前景的思路:
错误不一定要“捕获”,也可以像值一样被“解构”。
自定义封装:手写一个 safeAwait
语言层还没进化?那我们就自己造个轮子。
一个常见的思路是:将 Promise 的执行结果封装成一个三元组 [ok, err, data],结构明确,逻辑清晰。来看实现:
export type SafeAwaitResult<T> =
| [true, null, T]
| [false, Error, null];
exportasyncfunction safeAwait<T>(promise: Promise<T>): Promise<SafeAwaitResult<T>> {
try {
const result = await promise;
return [true, null, result];
} catch (err: any) {
const error = err instanceofError ? err : newError(String(err));
return [false, error, null];
}
}
使用时非常直观:
const [ok, err, user] = await safeAwait(fetchUser());
if (!ok) {
console.error('请求失败:', err);
return;
}
console.log('用户数据:', user);
这套封装的好处是显而易见的:
语义清晰:ok 表示状态,err 和 data 结构稳定 无 try/catch:逻辑更线性,阅读友好 类型明确:配合泛型推导,IDE 提示清晰 易于复用:在整个项目中统一处理异步异常
而且你还可以链式使用,避免回到嵌套地狱:
const [ok1, err1, user] = await safeAwait(fetchUser());
if (!ok1) return handle(err1);
const [ok2, err2, posts] = await safeAwait(fetchPosts(user.id));
if (!ok2) return handle(err2);
renderDashboard(user, posts);
这种写法非常适合搭配中间件、hooks 或服务层封装,逐渐成为许多项目的标准做法。
用库更香:await-to-js 一步到位
如果你不想自己封装,还有一个现成、稳定的库可以用:await-to-js
它的设计初衷和 safeAwait 类似,把 Promise 的结果转成 [error, result] 形式:
npm install await-to-js
使用方法如下:
import to from 'await-to-js';
const [err, data] = await to(fetchUser());
if (err) return handle(err);
render(data);
如果你的项目希望快速接入结构化的错误处理,不妨试试这个库。
总结对比:三种错误处理方案
| try/catch | |||
| safeAwait | |||
| await-to-js |
函数式时代的错误处理该进化了
在今天,继续用 try/catch 处理每一个异步错误,已经有些过时。无论是语言层面的提案,还是我们可以自己实现的封装,甚至是社区提供的优秀工具,目的都是一样的:让错误处理变得更清晰、更优雅、更现代。
再见了,重复的 try/catch,写更清爽的代码,从现在开始。
来源公众号:前端充电宝
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!