前端开发中,异步操作无处不在。比如从服务器拉取用户信息、提交表单数据、加载图片资源等。这些操作一旦多了,稍不注意就会让页面卡住或者直接报错崩溃。Promise 的出现让异步流程变得更可控,但很多人只用了 then,却忽略了错误处理这关键一环。
常见的 Promise 错误来源
网络请求超时、接口返回 500、JSON 解析失败,这些都可能让一个 Promise 变成 rejected 状态。如果没捕获,控制台就会打出一串红色错误,用户体验直接打折。
比如你写了个登录函数:
fetch('/api/login', { method: 'POST', body: JSON.stringify(data) })
.then(res => res.json())
.then(data => console.log('登录成功', data));
看着没问题,但如果网络断了,或者后端挂了,这个请求就会失败,而你没有任何提示,用户点完登录按钮像石沉大海。
用 catch 捕获错误
最直接的方式就是在 Promise 链最后加个 catch:
fetch('/api/login', { method: 'POST', body: JSON.stringify(data) })
.then(res => res.json())
.then(data => console.log('登录成功', data))
.catch(error => {
console.error('请求出错了:', error);
alert('登录失败,请检查网络或稍后再试');
});
这样无论前面哪一步出问题,都会被 catch 捕获。适合大多数场景,简单粗暴有效。
catch 不是万能的
如果你在 then 里又抛了错,比如手动写了 throw new Error(),或者 JSON 解析出错,catch 依然能接住。但要注意,如果链式调用中间有多个 then,前面的 catch 会终止后续的 then 执行,这其实是好事,避免错误蔓延。
async/await 中的 try-catch
现在更多人喜欢用 async/await 写异步代码,看起来更像同步。这时候错误处理就得靠 try...catch 了:
async function login() {
try {
const res = await fetch('/api/login', { method: 'POST', body: JSON.stringify(data) });
const data = await res.json();
console.log('登录成功', data);
} catch (error) {
console.error('登录失败:', error);
alert('提交失败,请重试');
}
}
这种写法逻辑更清晰,特别是连续多个异步操作时,不用一层层 then 套下去。但别忘了包 try,否则错误照样漏掉。
全局监听 unhandledrejection
即便写得再小心,也可能漏掉某些边缘情况。为了兜底,可以在全局监听未捕获的 Promise 错误:
window.addEventListener('unhandledrejection', event => {
console.warn('未处理的 promise 错误:', event.reason);
// 可以上报到监控系统
// event.preventDefault(); 阻止默认警告
});
上线项目建议加上这个,能帮你发现那些被忽略的异常。
实际场景:路由跳转前检查权限
在做前端路由时,经常需要在进入某个页面前检查用户是否登录。这个判断往往依赖异步请求。比如:
router.beforeEach(async (to, from, next) => {
if (to.meta.requiresAuth) {
try {
const response = await fetch('/api/user/profile');
if (response.ok) {
next();
} else {
next('/login');
}
} catch (error) {
console.error('权限检查失败', error);
next('/login'); // 出错也导向登录页
}
} else {
next();
}
});
这里如果不加 try-catch,一旦网络异常,整个路由守卫就卡住了,页面白屏。加上之后,哪怕请求失败,也能优雅降级到登录页。
错误处理不是可选项,而是保证程序健壮的必要手段。别让一个网络抖动毁了整个用户体验。