news 2026/4/16 14:35:52

Promise 与 async/await 错误处理最佳实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Promise 与 async/await 错误处理最佳实践指南

引言:为什么错误处理如此重要?

在异步编程中,错误处理常常被忽视,但它却是构建健壮应用的关键。想象一下:一个未处理的 Promise 拒绝可能导致整个应用崩溃,而良好的错误处理能提升用户体验并简化调试。本文将深入探讨从基础到高级的错误处理策略。

一、Promise 错误处理基础

1.1 基本的.catch()方法

javascript

// 基础用法 fetch('/api/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('请求失败:', error)); // 常见陷阱:过早的 .catch() fetch('/api/data') .catch(error => console.error('fetch失败')) // ❌ 会捕获所有后续错误 .then(response => response.json()) // 如果fetch失败,这里会继续执行 .then(data => console.log(data));

1.2 Promise 链中的精确错误处理

javascript

function processUserData(userId) { return fetchUser(userId) .then(user => { if (!user.active) { // 使用 throw 中断 Promise 链 throw new Error('用户未激活'); } return fetchUserProfile(user.id); }) .then(profile => { // 这里只处理 fetchUserProfile 的错误 return transformProfile(profile); }) .catch(error => { // 区分不同类型的错误 if (error.message === '用户未激活') { console.warn('跳过未激活用户'); return { skipped: true, userId }; } // 重新抛出未知错误 throw error; }); }

1.3 Promise.all 的错误处理策略

javascript

// 方法1:快速失败(任一失败即整体失败) async function fetchAllDataQuickFail(urls) { try { const promises = urls.map(url => fetch(url).then(r => r.json())); return await Promise.all(promises); } catch (error) { console.error('某个请求失败:', error); throw error; } } // 方法2:部分成功(使用 Promise.allSettled) async function fetchAllDataPartialSuccess(urls) { const promises = urls.map(url => fetch(url) .then(r => r.json()) .catch(error => ({ error, url })) ); const results = await Promise.allSettled(promises); const successful = results .filter(r => r.status === 'fulfilled') .map(r => r.value); const failed = results .filter(r => r.status === 'rejected') .map(r => r.reason); if (failed.length > 0) { console.warn(`${failed.length} 个请求失败`); } return { successful, failed }; }

二、async/await 错误处理模式

2.1 基本的 try-catch 模式

javascript

async function getUserData(id) { try { const user = await fetchUser(id); const profile = await fetchProfile(user.profileId); const posts = await fetchUserPosts(user.id); return { user, profile, posts }; } catch (error) { // 统一错误处理 console.error(`获取用户 ${id} 数据失败:`, error); // 返回降级数据 return { user: { id, name: 'Unknown' }, profile: null, posts: [], error: error.message }; } }

2.2 更细粒度的错误处理

javascript

async function processOrder(orderId) { let order, payment, shipping; try { order = await fetchOrder(orderId); } catch (error) { throw new Error(`订单 ${orderId} 不存在: ${error.message}`); } try { payment = await fetchPayment(order.paymentId); } catch (error) { console.warn(`支付信息获取失败,继续处理订单`); payment = null; } try { shipping = await calculateShipping(order); } catch (error) { // 使用默认运费 shipping = { cost: 0, estimatedDays: 7 }; } return { order, payment, shipping }; }

2.3 避免 try-catch 地狱的实用技巧

javascript

// 技巧1:使用高阶函数封装 function withRetry(fn, retries = 3) { return async function(...args) { let lastError; for (let i = 0; i < retries; i++) { try { return await fn(...args); } catch (error) { lastError = error; console.log(`尝试 ${i + 1}/${retries} 失败`); if (i < retries - 1) { await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)) ); } } } throw lastError; }; } // 技巧2:使用工具函数处理错误 function safeAwait(promise, fallbackValue = null) { return promise .then(data => ({ data, error: null })) .catch(error => ({ data: fallbackValue, error })); } async function fetchDataSafely() { const { data: users, error: usersError } = await safeAwait(fetchUsers()); const { data: products, error: productsError } = await safeAwait(fetchProducts(), []); if (usersError && productsError) { throw new Error('所有请求都失败了'); } return { users: users || [], products }; }

三、高级错误处理模式

3.1 错误边界与错误类型

javascript

// 定义自定义错误类型 class NetworkError extends Error { constructor(message, statusCode) { super(message); this.name = 'NetworkError'; this.statusCode = statusCode; this.isRetryable = statusCode >= 500; } } class ValidationError extends Error { constructor(message, field) { super(message); this.name = 'ValidationError'; this.field = field; } } // 使用错误类型 async function submitForm(data) { try { const response = await fetch('/api/submit', { method: 'POST', body: JSON.stringify(data) }); if (!response.ok) { throw new NetworkError( 'API请求失败', response.status ); } const result = await response.json(); if (result.errors) { throw new ValidationError( '数据验证失败', result.errors[0].field ); } return result; } catch (error) { // 根据错误类型采取不同策略 switch (error.name) { case 'NetworkError': if (error.isRetryable) { return retryOperation(() => submitForm(data)); } showToast('网络错误,请检查连接'); break; case 'ValidationError': highlightField(error.field); showToast(`请检查 ${error.field}`); break; default: logErrorToService(error); showToast('系统错误,请联系管理员'); } throw error; } }

3.2 全局错误处理

javascript

// 在应用入口设置全局 Promise 错误处理器 if (typeof window !== 'undefined') { // 捕获未处理的 Promise 拒绝 window.addEventListener('unhandledrejection', event => { event.preventDefault(); const { reason } = event; console.error('未处理的 Promise 拒绝:', reason); // 发送到错误监控服务 reportErrorToService(reason); // 用户友好的提示 if (reason instanceof NetworkError) { showNetworkErrorToast(); } }); // 全局错误边界(React示例) class GlobalErrorBoundary extends React.Component { componentDidCatch(error, errorInfo) { logErrorToService(error, errorInfo); // 可以在这里重置应用状态或显示错误页面 if (error instanceof NetworkError && error.isRetryable) { this.setState({ shouldRetry: true }); } } render() { if (this.state.shouldRetry) { return <RetryButton onClick={this.retry} />; } return this.props.children; } } }

3.3 并发与竞态条件的错误处理

javascript

function createCancelablePromise(promise) { let isCanceled = false; const wrappedPromise = new Promise((resolve, reject) => { promise.then( value => !isCanceled && resolve(value), error => !isCanceled && reject(error) ); }); return { promise: wrappedPromise, cancel: () => { isCanceled = true; } }; } async function searchWithDebounce(query) { // 取消之前的搜索请求 if (this.currentSearch) { this.currentSearch.cancel(); } this.currentSearch = createCancelablePromise( fetch(`/api/search?q=${query}`) .then(r => r.json()) ); try { const results = await this.currentSearch.promise; return results; } catch (error) { // 忽略被取消的请求的错误 if (!error.isCanceled) { throw error; } } }

四、实战:完整的 API 请求封装

javascript

class ApiClient { constructor(baseURL) { this.baseURL = baseURL; this.pendingRequests = new Map(); } async request(endpoint, options = {}) { const requestId = `${endpoint}-${Date.now()}`; const controller = new AbortController(); // 存储控制器以便后续取消 this.pendingRequests.set(requestId, controller); try { const response = await fetch(`${this.baseURL}${endpoint}`, { ...options, signal: controller.signal, headers: { 'Content-Type': 'application/json', ...options.headers, }, }); // 清理已完成的请求 this.pendingRequests.delete(requestId); if (!response.ok) { // 尝试解析错误信息 let errorMessage = `HTTP ${response.status}`; try { const errorData = await response.json(); errorMessage = errorData.message || errorMessage; } catch { // 忽略 JSON 解析错误 } throw new NetworkError(errorMessage, response.status); } // 处理空响应 const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { return await response.json(); } return await response.text(); } catch (error) { // 区分中止错误和其他错误 if (error.name === 'AbortError') { console.log('请求被取消:', endpoint); throw new Error('请求已取消'); } // 网络错误处理 if (error instanceof TypeError && error.message === 'Failed to fetch') { throw new NetworkError('网络连接失败,请检查网络设置', 0); } throw error; } finally { this.pendingRequests.delete(requestId); } } cancelRequest(requestId) { const controller = this.pendingRequests.get(requestId); if (controller) { controller.abort(); this.pendingRequests.delete(requestId); } } cancelAllRequests() { this.pendingRequests.forEach(controller => controller.abort()); this.pendingRequests.clear(); } // 带重试的请求 async requestWithRetry(endpoint, options, maxRetries = 3) { let lastError; for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await this.request(endpoint, options); } catch (error) { lastError = error; // 只有特定错误才重试 if (error instanceof NetworkError && error.isRetryable) { if (attempt < maxRetries - 1) { const delay = Math.min(1000 * Math.pow(2, attempt), 10000); await new Promise(resolve => setTimeout(resolve, delay)); continue; } } break; } } throw lastError; } }

五、测试错误处理

javascript

// 使用 Jest 测试错误处理 describe('API Client Error Handling', () => { test('处理网络错误', async () => { fetchMock.mockReject(new Error('Network error')); const client = new ApiClient('https://api.example.com'); await expect(client.request('/test')) .rejects .toThrow('网络连接失败'); }); test('处理 HTTP 错误状态', async () => { fetchMock.mockResponse('', { status: 404 }); const client = new ApiClient('https://api.example.com'); await expect(client.request('/not-found')) .rejects .toThrow('HTTP 404'); }); test('请求取消功能', async () => { const client = new ApiClient('https://api.example.com'); const requestId = 'test-request'; // 模拟长时间请求 fetchMock.mockResponse(() => new Promise(resolve => setTimeout(() => resolve({}), 1000) ) ); const requestPromise = client.request('/slow', {}, requestId); // 立即取消 client.cancelRequest(requestId); await expect(requestPromise) .rejects .toThrow('请求已取消'); }); });

六、最佳实践总结

✅ 该做的:

  1. 总是处理 Promise 拒绝:即使只是记录日志

  2. 使用自定义错误类型:区分业务错误和系统错误

  3. 提供有意义的错误信息:包含上下文,便于调试

  4. 实施优雅降级:当非关键功能失败时继续运行

  5. 记录生产环境错误:但不要暴露敏感信息

❌ 不该做的:

  1. 不要忽略错误:空 catch 块是反模式

  2. 不要过度包装 try-catch:保持错误处理接近可能出错的代码

  3. 不要暴露堆栈给用户:但在开发环境中要保留

  4. 不要阻塞 UI:长时间的错误处理应该在后台进行

  5. 不要假设网络总是可用:实现离线处理

📊 错误处理决策树:

text

出现错误 ├── 是网络错误? │ ├── 是 → 可重试? → 是 → 实施指数退避重试 │ │ └── 否 → 显示网络错误提示 │ └── 否 → 继续 ├── 是验证错误? │ ├── 是 → 高亮相关字段 │ └── 否 → 继续 ├── 是业务逻辑错误? │ ├── 是 → 显示用户友好消息 │ └── 否 → 继续 └── 是未知错误? ├── 记录到监控服务 ├── 显示通用错误消息 └── 保持应用可用状态

结语

良好的错误处理不仅是技术问题,更是产品思维。它关乎用户体验、系统稳定性和开发效率。通过实施这些最佳实践,你将能构建出更健壮、更可靠的前端应用。

记住:错误是不可避免的,但崩溃是可以避免的。优雅地处理错误,让你的应用在逆境中也能提供价值。


延伸阅读

  • MDN: Promise

  • Google: JavaScript 错误处理

  • [错误监控服务对比:Sentry vs Bugsnag vs Rollbar]

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 18:42:27

【EI复现】参与调峰的储能系统配置方案及经济性分析附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。&#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室&#x1f34a;个人信条&#xff1a;格物致知,完整Matlab代码及仿真咨询…

作者头像 李华
网站建设 2026/4/15 19:56:07

Langchain-Chatchat打造智慧图书馆服务体系

基于 Langchain-Chatchat 构建智慧图书馆服务体系 在高校与公共图书馆数字化转型的浪潮中&#xff0c;一个长期存在的矛盾日益凸显&#xff1a;馆藏资源越来越丰富&#xff0c;但读者“找得到却读不懂”“查得着却用不上”的问题却愈发严重。传统的关键词检索系统面对“贾宝玉的…

作者头像 李华
网站建设 2026/4/16 12:05:55

Vision Studio C#程序设计基础--多态函数重载、多态符号重载、抽象类、虚方法、密封类和静态类

多态函数重载面向对象四个特性: 封装 继承 多态 抽象多态: 同一个方法不同形态体现多态分静态多态和动态多态静态多态: 函数重载和符号重载动态多态:抽象和虚方法静态多态的函数重载:在同一个范围内,函数名一样,参数的类型不一样、参数的个数不一样,这样的函数就是重载仅仅只有…

作者头像 李华
网站建设 2026/4/15 23:37:13

计算机Java毕设实战-基于springboot的智能民宿预定与游玩系统设计与实现 “住宿 + 游玩” 一体化服务【完整源码+LW+部署说明+演示视频,全bao一条龙等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华