前端并发请求的深度解构从Promise.all到现代异步模式实战在构建现代复杂Web应用时一个页面往往需要同时向多个后端服务发起数据请求。想象一下电商首页的场景用户一进入页面需要立刻加载用户信息、推荐商品列表、促销活动、购物车数量、个性化广告等多个维度的数据。如果这些请求一个接一个地串行执行用户会明显感受到页面加载的迟滞体验大打折扣。这时候并发请求就成了提升前端性能的关键技术。但并发请求的实现方式远不止一种。很多开发者习惯性地使用Promise.all()也有人记得Axios曾经提供的axios.all()方法。这两种方式在底层机制、错误处理、资源管理上究竟有何差异在Vue3的Composition API生态下我们又该如何结合拦截器、响应式系统来构建更健壮、更高效的并发请求方案这篇文章将带你深入并发请求的每一个细节从原理到实践从基础用法到高级优化为你提供一套完整的解决方案。1. 并发请求的核心理解Promise.all的机制与局限Promise.all()是JavaScript原生提供的并发处理工具它的设计理念简洁而强大接收一个Promise数组当所有Promise都成功解决fulfilled时返回一个包含所有结果的数组如果其中任何一个Promise被拒绝rejected则立即抛出错误整个Promise.all()调用失败。1.1 Promise.all的基本使用模式让我们从一个典型的电商首页数据获取场景开始// 模拟电商首页需要并发获取的数据 const fetchHomePageData async () { const urls [ /api/user/profile, // 用户信息 /api/products/recommended, // 推荐商品 /api/promotions/active, // 活动信息 /api/cart/summary, // 购物车摘要 /api/notifications/unread // 未读通知 ]; try { const requests urls.map(url axios.get(url)); const results await Promise.all(requests); // 解构结果数组 const [userData, products, promotions, cart, notifications] results; return { user: userData.data, products: products.data, promotions: promotions.data, cart: cart.data, notifications: notifications.data }; } catch (error) { console.error(并发请求失败:, error); throw error; } };这种模式看似完美但它有一个致命的缺陷全有或全无的特性。如果五个请求中有一个失败比如购物车服务暂时不可用那么整个并发请求就会失败即使其他四个请求已经成功获取了数据。注意在需要确保所有数据都完整才能渲染页面的场景下这种特性是合理的。但在很多实际应用中我们可能希望部分失败不影响整体体验。1.2 Promise.allSettled的替代方案ES2020引入的Promise.allSettled()提供了更灵活的并发处理方式。它不会因为单个Promise失败而中断而是等待所有Promise都完成无论成功还是失败然后返回一个描述每个Promise状态的结果数组。const fetchHomePageDataWithGracefulDegradation async () { const endpoints [ { key: user, url: /api/user/profile }, { key: products, url: /api/products/recommended }, { key: promotions, url: /api/promotions/active }, { key: cart, url: /api/cart/summary }, { key: notifications, url: /api/notifications/unread } ]; const requests endpoints.map(({ key, url }) axios.get(url) .then(response ({ status: fulfilled, key, value: response.data })) .catch(error ({ status: rejected, key, reason: error.message })) ); const results await Promise.allSettled(requests); // 处理结果 const data {}; const errors {}; results.forEach(result { if (result.status fulfilled) { const { key, value } result.value; data[key] value; } else { const { key, reason } result.value; errors[key] reason; } }); return { data, errors }; };这种模式特别适合数据看板类应用即使某个数据源暂时不可用其他数据仍然可以正常展示并在界面上给出适当的错误提示。1.3 性能考量并发数限制与资源竞争无限制的并发请求可能导致浏览器或服务器的资源竞争。现代浏览器对同一域名的并发连接数有限制通常是6个过多的并发请求会导致排队反而降低性能。// 实现带并发限制的请求函数 const limitedConcurrentRequests async (urls, maxConcurrent 4) { const results new Array(urls.length); let currentIndex 0; // 执行一批请求 const runBatch async () { const batch []; const batchIndices []; // 收集当前批次 while (batch.length maxConcurrent currentIndex urls.length) { const index currentIndex; batchIndices.push(index); batch.push( axios.get(urls[index]) .then(response ({ index, data: response.data })) .catch(error ({ index, error })) ); } // 执行当前批次 const batchResults await Promise.allSettled(batch); // 存储结果 batchResults.forEach((result, i) { const originalIndex batchIndices[i]; if (result.status fulfilled) { results[originalIndex] { success: true, data: result.value.data }; } else { results[originalIndex] { success: false, error: result.value.error }; } }); return batch.length; }; // 分批执行所有请求 while (currentIndex urls.length) { await runBatch(); } return results; }; // 使用示例 const dashboardUrls [ /api/metrics/sales, /api/metrics/users, /api/metrics/conversion, /api/metrics/revenue, /api/metrics/retention, /api/metrics/churn, /api/metrics/engagement, /api/metrics/satisfaction ]; // 限制最多同时4个请求 const dashboardData await limitedConcurrentRequests(dashboardUrls, 4);这种分批并发的方式在大数据看板场景中特别有用可以平衡请求速度和系统负载。2. Axios并发生态从axios.all到现代实践Axios早期版本提供了axios.all()和axios.spread()这两个辅助方法但随着JavaScript原生Promise API的完善这些方法在Axios 0.21.0之后已被标记为废弃。了解它们的历史和替代方案有助于我们更好地理解并发请求的演进。2.1 axios.all的历史角色与现状在ES6 Promise普及之前Axios通过axios.all()提供了类似Promise.all()的功能配合axios.spread()可以方便地解构结果// 旧版Axios的并发请求方式已废弃 axios.all([ axios.get(/api/user/1), axios.get(/api/user/2), axios.get(/api/user/3) ]) .then(axios.spread((user1, user2, user3) { console.log(用户1:, user1.data); console.log(用户2:, user2.data); console.log(用户3:, user3.data); })) .catch(error { console.error(请求失败:, error); });axios.spread()的作用是将结果数组展开为独立的参数这在当时确实提供了一些便利。但随着JavaScript语言的发展我们现在有更优雅的方式// 现代替代方案 const [user1, user2, user3] await Promise.all([ axios.get(/api/user/1), axios.get(/api/user/2), axios.get(/api/user/3) ]); console.log(用户1:, user1.data); console.log(用户2:, user2.data); console.log(用户3:, user3.data);2.2 结合Axios拦截器的并发请求优化Axios拦截器为并发请求提供了强大的扩展能力。通过合理配置请求和响应拦截器我们可以实现统一的错误处理、重试机制、性能监控等功能。创建增强型Axios实例// utils/advancedAxios.js import axios from axios; // 创建基础实例 const advancedAxios axios.create({ baseURL: process.env.VUE_APP_API_BASE, timeout: 10000, headers: { Content-Type: application/json } }); // 请求计数器用于并发请求监控 let activeRequests 0; const maxConcurrentWarn 10; // 请求拦截器 advancedAxios.interceptors.request.use( config { activeRequests; // 监控并发数 if (activeRequests maxConcurrentWarn) { console.warn(高并发警告: 当前活跃请求数 ${activeRequests}); } // 添加请求时间戳 config.metadata { startTime: Date.now() }; // 统一添加认证信息 const token localStorage.getItem(auth_token); if (token) { config.headers.Authorization Bearer ${token}; } return config; }, error { activeRequests--; return Promise.reject(error); } ); // 响应拦截器 advancedAxios.interceptors.response.use( response { activeRequests--; // 计算请求耗时 const duration Date.now() - response.config.metadata.startTime; console.log(请求 ${response.config.url} 完成耗时 ${duration}ms); // 性能数据收集 if (duration 1000) { console.warn(慢请求警告: ${response.config.url} 耗时 ${duration}ms); } return response; }, error { activeRequests--; // 统一错误处理 if (error.response) { // 服务器响应错误 switch (error.response.status) { case 401: // 处理未授权 console.error(认证失败跳转登录); break; case 429: // 处理限流 console.warn(请求过于频繁稍后重试); break; case 500: // 服务器错误 console.error(服务器内部错误); break; } } else if (error.request) { // 请求发送失败 console.error(网络错误请求未送达); } else { // 其他错误 console.error(请求配置错误:, error.message); } return Promise.reject(error); } ); // 并发请求专用方法 advancedAxios.concurrent { // 带超时控制的并发请求 allWithTimeout: async (promises, timeout 15000) { const timeoutPromise new Promise((_, reject) { setTimeout(() reject(new Error(并发请求超时)), timeout); }); return Promise.race([ Promise.all(promises), timeoutPromise ]); }, // 分批执行并发请求 batch: async (requests, batchSize 3) { const results []; for (let i 0; i requests.length; i batchSize) { const batch requests.slice(i, i batchSize); const batchResults await Promise.allSettled(batch); results.push(...batchResults); // 批次间延迟避免对服务器造成压力 if (i batchSize requests.length) { await new Promise(resolve setTimeout(resolve, 100)); } } return results; } }; export default advancedAxios;2.3 拦截器在并发场景下的特殊考量当多个请求同时发起时拦截器的执行顺序和状态管理需要特别注意请求拦截器的执行顺序所有并发请求的拦截器会按照注册顺序依次执行共享状态管理避免在拦截器中修改共享状态可能导致竞态条件错误处理的粒度需要决定是在拦截器中统一处理错误还是在每个请求中单独处理// 并发请求中的拦截器最佳实践 const createConcurrentRequestHandler () { const requestQueue []; let isRefreshingToken false; // 处理token刷新的拦截器 advancedAxios.interceptors.response.use( response response, async error { const originalRequest error.config; // 如果是401错误且不是刷新token的请求 if (error.response?.status 401 !originalRequest._retry) { if (!isRefreshingToken) { isRefreshingToken true; try { // 刷新token const refreshResponse await advancedAxios.post(/auth/refresh); const newToken refreshResponse.data.token; localStorage.setItem(auth_token, newToken); // 重试所有队列中的请求 requestQueue.forEach(({ resolve }) resolve()); requestQueue.length 0; // 更新原始请求的token originalRequest.headers.Authorization Bearer ${newToken}; originalRequest._retry true; return advancedAxios(originalRequest); } catch (refreshError) { // 刷新失败清空队列并跳转登录 requestQueue.forEach(({ reject }) reject(refreshError)); requestQueue.length 0; window.location.href /login; return Promise.reject(refreshError); } finally { isRefreshingToken false; } } // 如果已经在刷新token将请求加入队列 return new Promise((resolve, reject) { requestQueue.push({ resolve: () { originalRequest.headers.Authorization Bearer ${localStorage.getItem(auth_token)}; resolve(advancedAxios(originalRequest)); }, reject }); }); } return Promise.reject(error); } ); return advancedAxios; };3. Vue3中的并发请求Composition API与现代模式Vue3的Composition API为我们提供了更灵活的状态管理和副作用处理方式结合并发请求可以构建出响应式、高效的数据获取层。3.1 使用Composition API封装并发请求// composables/useConcurrentFetch.js import { ref, computed, watchEffect } from vue; import advancedAxios from /utils/advancedAxios; export function useConcurrentFetch(endpoints, options {}) { const { immediate true, batchSize 3, retryCount 2, cacheTime 300000 // 5分钟缓存 } options; const data ref({}); const errors ref({}); const isLoading ref(false); const isFinished ref(false); const lastUpdated ref(null); // 缓存管理 const cache new Map(); const isCacheValid (key) { const cached cache.get(key); if (!cached) return false; const now Date.now(); return now - cached.timestamp cacheTime; }; // 带缓存的请求函数 const fetchWithCache async (key, url) { if (isCacheValid(key)) { return cache.get(key).data; } let retries retryCount; while (retries 0) { try { const response await advancedAxios.get(url); const result { data: response.data, timestamp: Date.now() }; cache.set(key, result); return response.data; } catch (error) { if (retries 0) throw error; retries--; await new Promise(resolve setTimeout(resolve, 1000 * (retryCount - retries))); } } }; // 执行并发请求 const execute async () { isLoading.value true; errors.value {}; try { const requests endpoints.map(({ key, url }) fetchWithCache(key, url) .then(result ({ key, result })) .catch(error ({ key, error })) ); // 分批执行请求 const results await advancedAxios.concurrent.batch(requests, batchSize); // 处理结果 results.forEach(result { if (result.status fulfilled) { const { key, result: data } result.value; data.value[key] data; } else { const { key, error } result.value; errors.value[key] error; } }); lastUpdated.value new Date(); isFinished.value true; } catch (error) { console.error(并发请求执行失败:, error); } finally { isLoading.value false; } }; // 手动刷新 const refresh () { cache.clear(); isFinished.value false; return execute(); }; // 自动执行 if (immediate) { watchEffect(() { if (!isFinished.value !isLoading.value) { execute(); } }); } // 计算属性是否有错误 const hasErrors computed(() Object.keys(errors.value).length 0); // 计算属性成功获取的数据数量 const successCount computed(() Object.keys(data.value).length); return { data, errors, isLoading, isFinished, lastUpdated, execute, refresh, hasErrors, successCount }; }3.2 在Vue组件中使用并发请求!-- DashboardComponent.vue -- template div classdashboard !-- 加载状态 -- div v-ifisLoading classloading-indicator 正在加载数据... progress :valuesuccessCount :maxendpoints.length/progress {{ successCount }} / {{ endpoints.length }} /div !-- 错误显示 -- div v-ifhasErrors classerror-alert h3部分数据加载失败/h3 ul li v-for(error, key) in errors :keykey {{ getEndpointName(key) }}: {{ error.message }} /li /ul button clickrefresh重试失败请求/button /div !-- 数据展示 -- div v-if!isLoading successCount 0 classdashboard-content div classmetric-card h4用户活跃度/h4 div classmetric-value{{ data.userMetrics?.activeUsers || -- }}/div /div div classmetric-card h4销售额/h4 div classmetric-value¥{{ formatCurrency(data.salesData?.total) }}/div /div div classmetric-card h4转化率/h4 div classmetric-value{{ data.conversionData?.rate || -- }}%/div /div !-- 更多数据卡片 -- /div !-- 最后更新时间 -- div v-iflastUpdated classlast-updated 最后更新: {{ formatTime(lastUpdated) }} button clickrefresh :disabledisLoading {{ isLoading ? 刷新中... : 刷新数据 }} /button /div /div /template script setup import { computed } from vue; import { useConcurrentFetch } from /composables/useConcurrentFetch; // 定义需要并发请求的端点 const endpoints [ { key: userMetrics, url: /api/dashboard/user-metrics }, { key: salesData, url: /api/dashboard/sales }, { key: conversionData, url: /api/dashboard/conversion }, { key: inventory, url: /api/dashboard/inventory }, { key: revenue, url: /api/dashboard/revenue }, { key: customerFeedback, url: /api/dashboard/feedback } ]; // 使用并发请求组合函数 const { data, errors, isLoading, isFinished, lastUpdated, refresh, hasErrors, successCount } useConcurrentFetch(endpoints, { batchSize: 4, retryCount: 1, cacheTime: 180000 // 3分钟缓存 }); // 辅助函数 const getEndpointName (key) { const names { userMetrics: 用户指标, salesData: 销售数据, conversionData: 转化数据, inventory: 库存信息, revenue: 收入统计, customerFeedback: 客户反馈 }; return names[key] || key; }; const formatCurrency (value) { if (!value) return 0.00; return Number(value).toLocaleString(zh-CN, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }; const formatTime (date) { return new Date(date).toLocaleTimeString(zh-CN); }; /script style scoped .dashboard { padding: 20px; } .loading-indicator { padding: 20px; background: #f5f5f5; border-radius: 8px; margin-bottom: 20px; } .error-alert { padding: 15px; background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 8px; margin-bottom: 20px; } .dashboard-content { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 20px; } .metric-card { padding: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .metric-value { font-size: 24px; font-weight: bold; margin-top: 10px; } .last-updated { text-align: center; color: #666; font-size: 14px; } /style3.3 响应式并发请求的优化技巧在Vue3中我们可以利用响应式系统的特性来优化并发请求的性能和用户体验按需加载使用watch或watchEffect监听依赖变化只在需要时发起请求请求去重避免相同请求的重复发送请求取消在组件卸载或依赖变化时取消未完成的请求请求优先级根据业务重要性调整请求顺序// composables/useSmartConcurrentFetch.js import { ref, watch, onUnmounted } from vue; import axios from axios; export function useSmartConcurrentFetch(dependencies, endpointsFactory) { const data ref({}); const errors ref({}); const isLoading ref(false); // 请求取消令牌 const cancelTokens new Map(); // 请求去重缓存 const requestCache new Map(); const CACHE_DURATION 30000; // 30秒 const createCancelToken (key) { // 如果已有相同请求在进行先取消 if (cancelTokens.has(key)) { cancelTokens.get(key).cancel(请求被新请求覆盖); } const source axios.CancelToken.source(); cancelTokens.set(key, source); return source.token; }; const fetchData async () { const endpoints endpointsFactory(dependencies); const now Date.now(); // 过滤出需要重新请求的端点 const endpointsToFetch endpoints.filter(({ key, url }) { const cached requestCache.get(key); if (!cached) return true; return now - cached.timestamp CACHE_DURATION; }); if (endpointsToFetch.length 0) { // 所有数据都在缓存有效期内 endpoints.forEach(({ key }) { if (requestCache.has(key)) { data.value[key] requestCache.get(key).data; } }); return; } isLoading.value true; try { const requests endpointsToFetch.map(({ key, url }) { const cancelToken createCancelToken(key); return axios.get(url, { cancelToken }) .then(response { const result { data: response.data, timestamp: now }; requestCache.set(key, result); data.value[key] response.data; return { key, success: true }; }) .catch(error { if (!axios.isCancel(error)) { errors.value[key] error; return { key, success: false, error }; } return { key, success: false, cancelled: true }; }); }); await Promise.allSettled(requests); // 从缓存中补充未重新请求的数据 endpoints.forEach(({ key }) { if (!data.value[key] requestCache.has(key)) { data.value[key] requestCache.get(key).data; } }); } finally { isLoading.value false; } }; // 监听依赖变化 watch( () [...dependencies], () { fetchData(); }, { immediate: true } ); // 组件卸载时取消所有请求 onUnmounted(() { cancelTokens.forEach(source { source.cancel(组件卸载请求取消); }); cancelTokens.clear(); }); // 手动刷新指定端点 const refreshEndpoint (key) { requestCache.delete(key); return fetchData(); }; // 手动刷新所有端点 const refreshAll () { requestCache.clear(); return fetchData(); }; return { data, errors, isLoading, refreshEndpoint, refreshAll }; }4. 高级并发模式与性能优化4.1 请求优先级调度在实际应用中不同数据的优先级可能不同。用户个人信息可能比推荐商品列表更重要核心业务数据可能比辅助数据更紧急。我们可以实现一个带优先级的请求调度器class PriorityRequestScheduler { constructor(maxConcurrent 6) { this.maxConcurrent maxConcurrent; this.activeCount 0; this.queue []; this.priorityLevels { HIGH: 0, MEDIUM: 1, LOW: 2 }; } add(request, priority MEDIUM, metadata {}) { return new Promise((resolve, reject) { const priorityValue this.priorityLevels[priority] || 1; this.queue.push({ request, priority: priorityValue, metadata, resolve, reject, timestamp: Date.now() }); // 按优先级和时间排序 this.queue.sort((a, b) { if (a.priority ! b.priority) { return a.priority - b.priority; } return a.timestamp - b.timestamp; }); this.processQueue(); }); } async processQueue() { while (this.activeCount this.maxConcurrent this.queue.length 0) { this.activeCount; const task this.queue.shift(); this.executeTask(task).finally(() { this.activeCount--; this.processQueue(); }); } } async executeTask({ request, resolve, reject, metadata }) { try { const startTime Date.now(); const response await request(); const duration Date.now() - startTime; // 记录性能指标 console.log(请求 ${metadata.name || 未知} 完成优先级: ${metadata.priority}, 耗时: ${duration}ms); resolve(response); } catch (error) { reject(error); } } clear() { this.queue []; } getQueueLength() { return this.queue.length; } getActiveCount() { return this.activeCount; } } // 使用示例 const scheduler new PriorityRequestScheduler(4); // 高优先级请求用户认证状态 const authRequest scheduler.add( () axios.get(/api/auth/status), HIGH, { name: 用户认证状态 } ); // 中优先级请求用户基本信息 const userInfoRequest scheduler.add( () axios.get(/api/user/profile), MEDIUM, { name: 用户信息 } ); // 低优先级请求推荐内容 const recommendationsRequest scheduler.add( () axios.get(/api/content/recommendations), LOW, { name: 推荐内容 } ); // 等待所有请求完成 const [authStatus, userInfo, recommendations] await Promise.all([ authRequest, userInfoRequest, recommendationsRequest ]);4.2 请求瀑布流与依赖处理有些并发请求之间存在依赖关系比如需要先获取用户ID然后才能获取用户的订单列表。这时候可以使用请求瀑布流模式// 处理有依赖关系的并发请求 const fetchDependentData async () { try { // 第一阶段获取基础数据 const [userInfo, preferences] await Promise.all([ axios.get(/api/user/info), axios.get(/api/user/preferences) ]); const userId userInfo.data.id; const userPreferences preferences.data; // 第二阶段基于用户ID获取相关数据 const [orders, notifications, recommendations] await Promise.all([ axios.get(/api/user/${userId}/orders), axios.get(/api/user/${userId}/notifications), axios.get(/api/recommendations, { params: { preferences: userPreferences } }) ]); // 第三阶段获取订单详情如果需要 const orderIds orders.data.map(order order.id); const orderDetails await Promise.all( orderIds.map(id axios.get(/api/orders/${id}/details) .catch(error ({ id, error: error.message })) ) ); return { user: userInfo.data, preferences: userPreferences, orders: orders.data, notifications: notifications.data, recommendations: recommendations.data, orderDetails: orderDetails.filter(detail !detail.error) }; } catch (error) { console.error(数据获取失败:, error); throw error; } };4.3 性能监控与优化指标为了确保并发请求的性能最优我们需要建立监控机制监控指标描述优化目标测量方法总请求时间从第一个请求开始到最后一个请求完成的时间 2秒performance.now()并发连接数同时活跃的HTTP连接数≤ 浏览器限制(通常6个)请求计数器请求成功率成功请求数 / 总请求数 99%错误监控缓存命中率缓存响应数 / 总请求数 60%缓存统计重试率需要重试的请求比例 5%重试计数器数据传输量所有请求的总数据大小最小化响应头Content-Length// 性能监控装饰器 const withPerformanceMonitoring (requestFunction, name unknown) { return async (...args) { const startTime performance.now(); const startMemory performance.memory?.usedJSHeapSize || 0; try { const result await requestFunction(...args); const endTime performance.now(); const endMemory performance.memory?.usedJSHeapSize || 0; const duration endTime - startTime; const memoryUsed endMemory - startMemory; // 记录性能指标 performanceMetrics.record({ name, duration, memoryUsed, success: true, timestamp: Date.now() }); // 慢请求警告 if (duration 1000) { console.warn(慢请求警告: ${name} 耗时 ${duration.toFixed(2)}ms); } return result; } catch (error) { const endTime performance.now(); performanceMetrics.record({ name, duration: endTime - startTime, success: false, error: error.message, timestamp: Date.now() }); throw error; } }; }; // 使用示例 const monitoredFetchUser withPerformanceMonitoring( (userId) axios.get(/api/users/${userId}), fetchUser ); const monitoredFetchOrders withPerformanceMonitoring( (userId) axios.get(/api/users/${userId}/orders), fetchOrders ); // 并发执行带监控的请求 const [user, orders] await Promise.all([ monitoredFetchUser(123), monitoredFetchOrders(123) ]);4.4 错误恢复与降级策略在复杂的并发场景中部分请求失败不应该导致整个应用崩溃。我们需要设计健壮的错误恢复机制class ResilientConcurrentFetcher { constructor(options {}) { this.maxRetries options.maxRetries || 3; this.retryDelay options.retryDelay || 1000; this.circuitBreakerThreshold options.circuitBreakerThreshold || 5; this.circuitBreakerTimeout options.circuitBreakerTimeout || 30000; this.failureCounts new Map(); this.circuitBreakerStates new Map(); } async fetchWithRetry(url, options {}) { const { retryCount 0, timeout 10000 } options; // 检查熔断器状态 if (this.isCircuitOpen(url)) { throw new Error(服务 ${url} 暂时不可用熔断器开启); } try { const controller new AbortController(); const timeoutId setTimeout(() controller.abort(), timeout); const response await fetch(url, { ...options, signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(HTTP ${response.status}); } // 请求成功重置失败计数 this.recordSuccess(url); return await response.json(); } catch (error) { // 记录失败 this.recordFailure(url); // 检查是否需要开启熔断 if (this.shouldOpenCircuit(url)) { this.openCircuit(url); throw new Error(服务 ${url} 暂时不可用连续失败次数过多); } // 重试逻辑 if (retryCount this.maxRetries) { const delay this.retryDelay * Math.pow(2, retryCount); console.log(请求 ${url} 失败${delay}ms后重试 (${retryCount 1}/${this.maxRetries})); await new Promise(resolve setTimeout(resolve, delay)); return this.fetchWithRetry(url, { ...options, retryCount: retryCount 1 }); } throw error; } } async fetchAll(urls, options {}) { const { fallbackValues {}, priority [] } options; const requests urls.map(url this.fetchWithRetry(url, options) .then(data ({ url, data, success: true })) .catch(error { console.error(请求 ${url} 失败:, error.message); // 使用降级值 if (fallbackValues[url]) { return { url, data: fallbackValues[url], success: false, error: error.message, isFallback: true }; } return { url, error: error.message, success: false }; }) ); // 按优先级排序请求 if (priority.length 0) { requests.sort((a, b) { const aIndex priority.indexOf(a.url); const bIndex priority.indexOf(b.url); return (aIndex -1 ? Infinity : aIndex) - (bIndex -1 ? Infinity : bIndex); }); } const results await Promise.allSettled(requests); return results.map(result result.status fulfilled ? result.value : result.reason ); } recordFailure(url) { const count (this.failureCounts.get(url) || 0) 1; this.failureCounts.set(url, count); } recordSuccess(url) { this.failureCounts.set(url, 0); this.circuitBreakerStates.delete(url); } shouldOpenCircuit(url) { const failureCount this.failureCounts.get(url) || 0; return failureCount this.circuitBreakerThreshold; } isCircuitOpen(url) { const state this.circuitBreakerStates.get(url); if (!state) return false; if (Date.now() - state.openedAt this.circuitBreakerTimeout) { // 超时后进入半开状态 this.circuitBreakerStates.set(url, { ...state, state: half-open }); return false; } return state.state open; } openCircuit(url) { this.circuitBreakerStates.set(url, { state: open, openedAt: Date.now() }); console.warn(熔断器开启: ${url} 将在 ${this.circuitBreakerTimeout}ms 后恢复); // 定时器自动关闭熔断器 setTimeout(() { this.circuitBreakerStates.delete(url); console.log(熔断器关闭: ${url} 恢复服务); }, this.circuitBreakerTimeout); } } // 使用示例 const fetcher new ResilientConcurrentFetcher({ maxRetries: 2, retryDelay: 1000, circuitBreakerThreshold: 3 }); const urls [ /api/user/profile, /api/products, /api/promotions, /api/cart ]; const fallbackValues { /api/promotions: { active: [], upcoming: [] }, /api/cart: { items: [], total: 0 } }; const results await fetcher.fetchAll(urls, { fallbackValues, priority: [/api/user/profile, /api/cart] // 用户信息和购物车优先 }); // 处理结果 const successful results.filter(r r.success); const failed results.filter(r !r.success); const usingFallback results.filter(r r.isFallback); console.log(成功: ${successful.length}, 失败: ${failed.length}, 使用降级: ${usingFallback.length});在实际项目中我经常需要处理数十个并发请求的数据看板最初使用简单的Promise.all()经常因为某个不稳定的第三方服务导致整个页面加载失败。后来逐步引入了Promise.allSettled()、请求优先级调度和熔断机制现在即使部分服务暂时不可用核心功能依然能正常使用只是相关模块显示降级内容或加载状态。这种渐进式增强的策略显著提升了用户体验和系统稳定性特别是在微服务架构下某个服务的短暂故障不再会导致整个应用崩溃。