尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JavaScript Promise 错误处理全指南

JavaScript Promise 错误处理全指南 1. Promise 未捕获 reject 错误的本质与危害当你在 JavaScript 中使用 Promise 时最危险的陷阱莫过于未处理的 reject 错误。这种错误不会立即导致程序崩溃而是会悄无声息地潜伏在代码中直到某个关键时刻突然爆发。控制台常见的 Uncaught (in promise) Error 就是这类问题的典型表现。Promise 的设计哲学是要么成功(resolve)要么失败(reject)但很多开发者只关注成功路径的处理忽略了 reject 情况的捕获。这种疏忽在实际项目中可能导致关键业务逻辑中断却不报错用户操作无响应但界面看似正常内存泄漏和资源未释放错误日志缺失难以追踪问题根源更棘手的是这类错误在不同浏览器中的表现也不一致。Chrome 会打印错误但继续执行而某些移动端浏览器可能直接静默失败。这种不确定性使得问题在测试阶段更难被发现。2. 基础捕获方案.catch() 的正确使用姿势最基本的错误处理方式是为每个 Promise 链添加 .catch() 处理器fetch(/api/data) .then(response response.json()) .then(data processData(data)) .catch(error { console.error(请求失败:, error); showErrorUI(数据加载失败请重试); });但实际开发中有几点常被忽视的细节catch 的位置决定捕获范围catch 只会捕获它前面 then 中的错误。如果把 catch 放在链式中间后面的 then 又可能产生新的未捕获错误。重新抛出错误有时我们需要在 catch 中处理错误后继续向上抛出.catch(error { logErrorToServer(error); // 先记录错误 throw error; // 继续抛出 })异步 catch 的问题catch 回调本身也可能抛出错误这时需要使用嵌套的 catch.catch(error { return asyncLogError(error) // 这个操作本身也是异步的 .catch(loggingError { console.error(连错误日志都失败了:, loggingError); }); })3. 高级处理模式全局捕获与错误边界对于大型应用仅靠局部 catch 是不够的。我们需要建立多层次的防御体系3.1 unhandledrejection 事件监听浏览器提供了全局捕获 Promise 错误的机制window.addEventListener(unhandledrejection, event { const { reason, promise } event; // reason 是 reject 的原因 // promise 是出错的 Promise 实例 trackError(reason); // 上报到监控系统 event.preventDefault(); // 阻止默认控制台报错 });重要提示某些浏览器中即使添加了 unhandledrejection 监听器错误仍会打印到控制台。生产环境应该配合错误监控系统使用。3.2 Promise 错误边界Error Boundary借鉴 React 的错误边界概念可以创建 Promise 的包装器function safePromise(promise, context {}) { return promise.catch(error { enrichError(error, context); // 添加上下文信息 throw error; // 继续抛出供上层处理 }); } // 使用方式 safePromise(fetch(/data), { page: home }) .then(/*...*/);3.3 组合式错误处理对于复杂异步流建议采用如下模式async function fetchWithRetry(url, retries 3) { try { const response await fetch(url); if (!response.ok) throw new Error(HTTP ${response.status}); return response.json(); } catch (error) { if (retries 0) throw error; await delay(1000); return fetchWithRetry(url, retries - 1); } }4. 常见陷阱与实战解决方案4.1 Promise 构造函数中的错误// 危险写法 new Promise(() { throw new Error(构造函数内错误); }); // 正确写法 new Promise((resolve, reject) { try { // 业务逻辑 } catch (error) { reject(error); } });4.2 并行操作中的错误处理Promise.all 在任一 promise reject 时会立即 reject// 传统方式 - 一个失败全盘皆输 Promise.all([p1, p2, p3]).catch(/*...*/); // 改进方案 - 等所有 promise 完成 Promise.allSettled([p1, p2, p3]).then(results { const errors results.filter(r r.status rejected); if (errors.length) { handlePartialFailure(errors); } });4.3 async/await 的 try/catch 误区// 反模式 - 混合同步/异步错误 async function example() { try { return await fetchData(); } catch (error) { // 这里会捕获 fetchData 的 reject // 但不会捕获 fetchData 中的同步错误 } } // 正确模式 async function example() { try { const data syncOperation(); // 可能抛出同步错误 return await fetchData(data); // 可能抛出异步错误 } catch (error) { // 捕获所有类型的错误 } }5. 生产环境最佳实践5.1 错误分类处理建立错误分类体系区分可恢复错误如网络超时业务逻辑错误如权限不足致命错误如数据格式异常class AppError extends Error { constructor(message, type system) { super(message); this.type type; } } // 使用示例 fetch(/api).catch(error { if (error instanceof AppError) { handleBusinessError(error); } else { handleSystemError(error); } });5.2 错误上下文增强在错误对象中添加执行上下文async function getUserProfile(userId) { const context { userId, timestamp: Date.now() }; try { return await fetchProfile(userId); } catch (error) { error.context context; throw error; } }5.3 监控与报警策略配置合理的监控阈值高频出现的相同错误关键路径上的首次错误特定用户群体的错误聚集window.addEventListener(unhandledrejection, event { const error event.reason; if (isCriticalError(error)) { sendImmediateAlert(error); } else { queueErrorForReporting(error); } });6. 测试策略与调试技巧6.1 强制触发 reject 的测试方法// 使用 Sinon.js 模拟 reject const stub sinon.stub(api, fetchData).rejects(new Error(mock error)); // 或者使用 Jest jest.spyOn(api, fetchData).mockRejectedValue(new Error(mock error));6.2 Chrome 调试技巧开启 Pause on caught exceptions 和 Pause on uncaught exceptions使用 console.error 打印完整的错误链对特定 Promise 添加临时 catch 进行调试6.3 错误重现策略对于难以复现的异步错误记录用户操作序列保存网络请求的快照使用 Promise 执行追踪工具function tracedPromise(promise, name) { console.log([Promise] ${name} started); return promise .then(value { console.log([Promise] ${name} resolved); return value; }) .catch(error { console.log([Promise] ${name} rejected, error); throw error; }); }在实际项目中我通常会建立一个中央错误处理模块统一管理所有 Promise 错误。这个模块负责错误的上报、分类和恢复策略。对于关键业务流还会添加事务ID贯穿整个异步调用链这样当错误发生时可以完整重建执行上下文。记住好的错误处理不是阻止错误发生而是确保当错误发生时系统能以可预测的方式降级运行并为开发者提供足够的诊断信息。
返回列表