在现代JavaScript开发中,async/await
与try...catch
的组合是处理异步错误的黄金搭档。本文通过7个真实项目场景,带你深入理解如何优雅地处理Promise错误,提升代码健壮性。
🔒 案例1:用户认证与权限验证
场景:登录流程中验证用户身份并获取权限信息,任一环节失败都需向用户展示明确提示。
async function login(username, password) {
try {
// 1. 发送登录请求
const authResponse = await fetch('/api/auth/login', {
method: 'POST',
body: JSON.stringify({ username, password })
});
if (!authResponse.ok) {
throw new Error('用户名或密码错误');
}
const { token } = await authResponse.json();
localStorage.setItem('auth_token', token);
// 2. 使用token获取用户信息
const userResponse = await fetch('/api/user/me', {
headers: { Authorization: `Bearer ${token}` }
});
if (!userResponse.ok) {
throw new Error('获取用户信息失败');
}
const userData = await userResponse.json();
return userData;
} catch (error) {
// 统一处理登录流程中的错误
console.error('登录失败:', error);
localStorage.removeItem('auth_token'); // 清理无效token
// 根据错误类型展示不同提示
if (error.message.includes('密码错误')) {
showToast('用户名或密码不正确', 'error');
} else if (error.message.includes('网络')) {
showToast('网络连接异常,请重试', 'warning');
} else {
showToast('登录失败,请稍后再试', 'error');
}
return null;
}
}
关键技巧:
- 链式异步操作的统一错误捕获
- 根据错误类型提供差异化用户反馈
- 失败时的资源清理(如移除无效token)
📊 案例2:批量数据处理与错误隔离
场景:从多个API获取数据并处理,允许部分失败但需记录所有错误。
async function fetchDashboardData() {
const errors = [];
let dashboardData = {};
try {
// 并行请求多个数据
const [userPromise, postsPromise, statsPromise] = [
fetchUser().catch(err => {
errors.push({ type: 'user', message: err.message });
return null; // 失败时返回默认值
}),
fetchPosts().catch(err => {
errors.push({ type: 'posts', message: err.message });
return [];
}),
fetchStats().catch(err => {
errors.push({ type: 'stats', message: err.message });
return {};
})
];
// 等待所有请求完成(无论成功或失败)
const [user, posts, stats] = await Promise.all([
userPromise, postsPromise, statsPromise
]);
dashboardData = { user, posts, stats };
} catch (error) {
// 处理Promise.all可能抛出的未预期错误
errors.push({ type: 'critical', message: error.message });
} finally {
// 记录所有错误(即使部分数据获取成功)
if (errors.length > 0) {
logErrors('dashboard_fetch', errors);
}
}
return dashboardData;
}
关键技巧:
- 单个Promise的局部错误处理
- 使用
Promise.all
并行处理多个请求 - 错误信息的收集与分类记录
- 失败请求的默认值回退策略
📤 案例3:文件上传与进度跟踪
场景:上传大文件时监听进度,处理中断或失败情况。
async function uploadFile(file) {
try {
const formData = new FormData();
formData.append('file', file);
// 创建可取消的fetch请求
const controller = new AbortController();
const signal = controller.signal;
// 显示上传进度
showUploadProgress(0);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
signal,
// 自定义headers以获取进度
headers: { 'X-Progress': 'true' }
});
if (!response.ok) {
throw new Error(`上传失败: ${response.status}`);
}
const result = await response.json();
showUploadSuccess(result);
return result;
} catch (error) {
// 处理上传错误
if (error.name === 'AbortError') {
showToast('上传已取消', 'info');
} else {
showToast(`上传失败: ${error.message}`, 'error');
retryUpload(file); // 可实现自动重试
}
}
}
关键技巧:
- 使用
AbortController
实现请求取消 - 上传进度的实时监控与展示
- 区分不同类型的上传错误(如用户取消、网络错误)
- 失败后的自动重试机制
💾 案例4:数据库事务处理
场景:执行多个数据库操作,若任一失败则回滚所有更改。
async function updateUserProfile(userId, profileData) {
let transaction;
try {
// 1. 开始数据库事务
transaction = await db.beginTransaction();
// 2. 更新用户基本信息
await transaction.execute(
'UPDATE users SET name=?, email=? WHERE id=?',
[profileData.name, profileData.email, userId]
);
// 3. 更新用户偏好设置(可能失败)
await transaction.execute(
'INSERT INTO preferences (user_id, theme, notifications) VALUES (?, ?, ?)',
[userId, profileData.theme, profileData.notifications]
);
// 4. 提交事务
await transaction.commit();
return { success: true };
} catch (error) {
// 5. 回滚事务(若已开始)
if (transaction) {
await transaction.rollback();
console.log('事务已回滚');
}
// 记录错误并分类处理
console.error('更新用户资料失败:', error);
if (error.code === 'ER_DUP_ENTRY') {
showToast('该邮箱已被注册', 'warning');
} else {
showToast('服务器内部错误,请稍后重试', 'error');
}
return { success: false, error };
}
}
关键技巧:
- 使用事务保证数据一致性
- 失败时的事务回滚操作
- 数据库错误码的识别与处理
- 用户友好的错误提示
🔄 案例5:第三方API调用与重试机制
场景:调用不稳定的第三方API时实现指数退避重试策略。
async function callThirdPartyAPI(endpoint, data, maxRetries = 3) {
let retries = 0;
while (retries < maxRetries) {
try {
const response = await fetch(`https://thirdparty.com/${endpoint}`, {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' }
});
if (!response.ok) {
throw new Error(`API响应错误: ${response.status}`);
}
return await response.json();
} catch (error) {
retries++;
console.error(`API调用失败 (尝试 ${retries}/${maxRetries}):`, error);
if (retries >= maxRetries) {
// 达到最大重试次数,记录致命错误并抛出
logFatalError('third_party_api', error);
throw new Error(`API调用失败,已尝试 ${maxRetries} 次`);
}
// 指数退避:每次重试等待时间加倍
const delay = 500 * Math.pow(2, retries);
console.log(`等待 ${delay}ms 后重试...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
关键技巧:
- 指数退避重试算法实现
- 最大重试次数限制
- 永久性错误的快速失败策略
- 重试间隔的动态调整
🏭 案例6:复杂工作流编排
场景:按顺序执行多个依赖步骤,前一步结果作为后一步输入。
async function processOrder(orderId) {
try {
// 1. 获取订单详情
const order = await getOrderDetails(orderId);
if (!order) throw new Error('订单不存在');
// 2. 验证库存
const hasStock = await checkInventory(order.items);
if (!hasStock) throw new Error('库存不足');
// 3. 处理支付
const paymentResult = await processPayment(order.total);
if (!paymentResult.success) {
throw new Error(`支付失败: ${paymentResult.message}`);
}
// 4. 更新订单状态
const updatedOrder = await updateOrderStatus(orderId, 'PAID');
// 5. 发送通知
await sendOrderConfirmation(order.customer.email);
return updatedOrder;
} catch (error) {
// 记录错误并执行补偿操作
console.error('订单处理失败:', error);
// 根据错误类型执行不同的回滚操作
if (error.message.includes('支付')) {
await cancelOrderReservation(orderId);
}
// 通知管理员
await notifyAdmin('order_processing_failure', {
orderId,
errorMessage: error.message
});
return { error: error.message };
}
}
关键技巧:
- 依赖步骤的顺序执行与错误中断
- 基于错误类型的补偿操作
- 业务流程中的条件验证
- 管理员通知机制
⏰ 案例7:定时任务与资源清理
场景:定期执行数据同步任务,确保资源释放。
async function syncData() {
let connection;
let lock;
try {
// 1. 获取分布式锁,防止多实例同时执行
lock = await acquireLock('data_sync');
if (!lock) throw new Error('无法获取同步锁');
// 2. 建立数据库连接
connection = await createDbConnection();
// 3. 从外部API获取数据
const externalData = await fetchExternalData();
// 4. 开始事务
await connection.beginTransaction();
// 5. 清理旧数据
await connection.execute('DELETE FROM sync_data WHERE timestamp < ?', [Date.now() - 86400000]);
// 6. 插入新数据
await batchInsert(connection, 'sync_data', externalData);
// 7. 提交事务
await connection.commit();
console.log('数据同步完成');
} catch (error) {
// 回滚事务(如果已开始)
if (connection && connection.inTransaction) {
await connection.rollback();
}
console.error('数据同步失败:', error);
logSystemError('data_sync', error);
} finally {
// 释放资源(无论成功或失败)
if (connection) await connection.close();
if (lock) await releaseLock(lock);
}
}
关键技巧:
- 分布式锁的获取与释放
- 数据库连接的正确关闭
- 事务的提交与回滚
- 使用
finally
确保资源清理
🚀 关键实践总结
技巧 | 描述 |
---|---|
错误边界设计 | 在业务流程的关键节点设置错误捕获,避免错误蔓延 |
失败恢复机制 | 对可重试的操作实现指数退避重试,提高系统韧性 |
资源清理 | 使用finally 确保数据库连接、文件句柄等资源正确释放 |
错误分类处理 | 区分临时性错误(网络波动)和永久性错误(参数错误),采取不同策略 |
用户体验优化 | 根据错误类型向用户展示不同级别的反馈,提供清晰的操作指引 |
监控与日志 | 记录详细的错误上下文(如错误类型、时间戳、相关参数),便于后续排查 |
🌟 总结
通过这7个真实案例,我们可以看到async/await
与try...catch
的组合在不同场景下的强大威力。合理运用这些技术,不仅能让异步代码更易读,还能大幅提升系统的健壮性和用户体验。
在实际开发中,建议根据业务场景选择合适的错误处理策略,形成标准化的错误处理模式,让代码在面对各种异常情况时都能保持稳定运行。