真正优雅的降级是主动退避+快速失败+降级响应,关键在决策节奏与退出时机;需封装支持指数退避、熔断、超时的异步函数,catch仅兜底最终失败,返回结构化错误并避免二次await。
当基础网关完全瘫痪时,单纯靠 try-catch 捕获异常无法解决请求堆积、重试风暴或雪崩问题。真正优雅的降级,是让 await 在失败后不卡死、不盲重试、不拖垮下游,而是主动退避 + 快速失败 + 降级响应。关键不在“捕获”,而在“决策节奏”和“退出时机”。
不要把整个 fetch 或 axios 请求裸写在 await 后面。应封装成一个支持指数退避的异步函数,try-catch 只负责兜底最终失败,不参与重试逻辑。
retryWithBackoff),它内部用 Promise 链 + setTimeout 实现延迟,而非 await 堆叠try-catch 包裹的是“带退避的最终调用结果”,不是每次重试 —— 这样能避免 catch 被频繁触发干扰主流程await retryWithBackoff(apiCall, { maxRetries: 3, baseDelay: 100 }),成功则返回数据;失败才进 catch 走降级逻辑网关完全瘫痪 ≠ 网络延迟高,而是持续返回连接拒绝(ECONNREFUSED)、超时(ETIMEDOUT)或 503。此时指数退避若无熔断,会越等越久;若无单次超时,一次请求就卡死整个 await。
AbortSignal(如 AbortController.timeout(800)),确保单次请求不超 800msMath.min(baseDelay * 2 ** attempt, maxDelay),上限建议设为 2–3 秒,防止退避过长导致用户感知卡顿网关瘫痪时,catch 里不能只返回空对象或默认值,需明确传递系统状态,让上游决定是展示缓存、兜底页面,还是提示“服务暂不可用”。
throw new GatewayUnavailableError('gateway_down', { cause: 'connect ECONNREFUSED' })
catch 中根据错误类型快速返回:静态兜底数据、本地缓存(带 stale 标记)、或统一错误状态码(如 { code: 503, message: '网关维护中' })await 其他可能失败的服务(如日志上报),改用 fire-and-forget 方式异步记录即使网关恢复,用户也可能已离开页面。若 await 正在退避等待中,应支持外部取消,避免无效等待占用资源。
signal 参数,与 AbortSignal 集成,在组件卸载或路由跳转时主动中止await 表达式外层加 Promise.race([call, timeoutPromise]) 是冗余的 —— 正确做法是把超时/取消逻辑下沉到重试函数内部setTimeout + resolve,统一用 Promise.resolve(…) 或 Promise.reject(…) 保证行为可预测