2023年Q3,某电商平台大促期间,一个未捕获的TypeError让支付页面白屏了47分钟。直接损失:1200万订单流失。技术负责人复盘时发现,问题根源只是少写了一个try块。
这不是个例。Chrome DevTools的崩溃日志显示,前端未处理异常占生产环境故障的34%,而修复成本是开发阶段的15倍。
try/catch:给代码买份保险
JavaScript的try...catch结构本质上是个"安全气囊"。代码撞墙时,它决定用户看到的是友好提示还是控制台红字。
基础语法很简单:把可能出错的代码塞进try,出错后的兜底逻辑放进catch。
看个真实场景。调用第三方API获取用户数据:
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error("User not found");
const data = await response.json();
renderUserProfile(data);
} catch (error) {
console.error("Fetch failed:", error);
displayFallbackUI("Couldn't load profile. Showing demo data instead.");
}
}
关键点在于第9行。没有这行,网络抖动或接口404会让整个页面挂掉。有了它,用户看到演示数据,业务流程继续走。
这种"优雅降级"(Graceful Degradation)在用户体验层面是隐形修复——用户甚至不会意识到出过问题。
finally:那个不管输赢都要执行的人
finally块像个强迫症管家:无论try里的代码成功还是catch捕获了错误,它都会执行。
典型场景是资源清理。比如加载动画:
let loadingSpinner = showLoading();
try {
const data = await fetchHeavyData();
renderChart(data);
} catch (err) {
showErrorToast("数据加载失败");
} finally {
hideLoading(loadingSpinner); // 必定执行
}
没有finally,你可能要在try和catch里各写一遍hideLoading,漏掉一个就是内存泄漏。
更隐蔽的陷阱是异步代码。Promise链里的finally和try/catch/finally的finally行为一致,但很多人混用导致竞态条件。
错误类型:对症下药比万能药管用
JavaScript内置了7种错误类型,盲目catch所有错误等于关掉烟雾报警器。
ReferenceError(引用不存在变量)通常是代码缺陷,应该暴露出来让开发者修复;TypeError(类型操作非法)可能是数据格式问题,可以降级处理;RangeError(数值越界)常见于递归溢出或API参数错误。
实际项目中,建议分层捕获:
try {
riskyOperation();
} catch (err) {
if (err instanceof TypeError) {
// 数据问题,用默认值兜底
useDefaultValue();
} else if (err instanceof ReferenceError) {
// 代码缺陷,上报监控
reportToSentry(err);
throw err; // 重新抛出,避免静默失败
} else {
// 未知错误,统一处理
showGenericError();
}
}
这种区分在大型应用里能节省大量调试时间。Sentry的2024年报告显示,带类型判断的错误处理让平均修复时间(MTTR)缩短了62%。
async/await的暗礁:别在try里await裸Promise
一个常见反模式:在try块里直接await多个独立Promise。
try {
const user = await fetchUser(); // 如果这里挂了
const orders = await fetchOrders(); // 这行永远不会执行
const stats = await fetchStats();
} catch (e) { /* ... */ }
三个请求串行执行,失败一个全部放弃。更好的做法是Promise.allSettled:
const [userRes, ordersRes, statsRes] = await Promise.allSettled([
fetchUser(), fetchOrders(), fetchStats()
]);
每个请求独立成败,业务层决定哪些失败可接受、哪些需要阻断流程。
这种"部分成功"模式在电商结算页尤其重要——地址接口挂了可以用默认地址,支付接口挂了必须阻断。
最后留个思考题:你的项目里有多少try块是空的catch,或者只是console.log(err)?这种"假装处理了"的代码,比完全不处理更危险——它让错误从监控雷达里消失,却在用户端继续搞破坏。
热门跟贴