Node.js/TypeScript中Promise.allSettled的重试与失败日志最佳实践
Promise失败日志记录与重试逻辑优化方案
问题核心
调用第三方服务的多个Promise请求偶尔导致应用无响应,调用方超时;已添加日志但无输出,重试逻辑无效,需要解决:
- 正确记录Promise失败的方式
- 最优整合重试、日志记录、3次重试失败后返回null的逻辑
- 修复无日志、无响应的问题
现有代码的关键问题
- 重试逻辑无效:
promiseRetry传入的是已执行的Promise实例,重试时复用同一个已完成(pending/resolved/rejected)的Promise,不会重新发起请求 - 异步错误未捕获:
searchPatientPromise中externalService.search()是异步操作但未加await,同步try/catch无法捕获Promise拒绝错误,导致无日志 - 日志信息错误:
getSettledPromiseValue参数名错误(传入retryCount却用encounterId字段),日志定位信息不准确 - 冗余处理:
Promise.allSettled与promiseRetry的逻辑重复,增加代码复杂度
一、记录失败Promise的正确方式
- 全覆盖错误捕获:所有异步Promise必须通过
await配合try/catch,或.catch()处理拒绝,避免未捕获的Promise拒绝导致静默失败 - 日志要包含关键信息:错误栈、请求参数、重试次数、资源类型等,方便快速定位问题
- 不吞掉错误:即使最终要返回null,也必须先完整记录错误详情,不能仅输出简单提示
二、重试、日志、失败返回null的最优结合实现
1. 通用重试工具函数(核心)
封装重试、日志、失败返回逻辑,支持传入请求函数(确保重试时重新发起请求):
export const withRetry = async <T>( requestFn: () => Promise<T>, maxRetries: number, logger: PinoLogger, context: Record<string, any> = {} ): Promise<T | null> => { let attempt = 0; while (attempt <= maxRetries) { try { const result = await requestFn(); logger.info(`请求成功,尝试次数: ${attempt + 1}`, context); return result; } catch (error) { attempt++; const errorDetails = { ...context, attempt, maxRetries, errorMessage: error instanceof Error ? error.message : String(error), errorStack: error instanceof Error ? error.stack : undefined }; if (attempt <= maxRetries) { logger.error(`请求失败,即将重试,尝试次数: ${attempt}`, errorDetails); // 可选:指数退避,避免短时间重复请求给第三方服务施压 await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt - 1))); } else { logger.error(`请求失败,已达最大重试次数`, errorDetails); return null; } } } return null; };
2. 并行处理多个请求
简化多Promise处理逻辑,并行执行所有带重试的请求:
export const resolvePromises = async <T>( requestFns: (() => Promise<T>)[], maxRetries: number, logger: PinoLogger ): Promise<(T | null)[]> => { return Promise.all( requestFns.map((fn, index) => withRetry(fn, maxRetries, logger, { requestIndex: index, requestName: fn.name }) ) ); };
3. 修正search*方法的错误捕获
确保异步错误被正确捕获并抛出,让重试工具处理:
async searchPatientPromise(): Promise<any> { const resourceType = 'Patient'; const searchParams = { /* 你的查询参数 */ }; try { // 必须加await,捕获异步请求的错误 const resource = await externalService.search(); this.logger.info('searchPatientPromise 调用成功', { resourceType, searchParams }); return resource; } catch (err) { const error = err instanceof Error ? err : new Error(String(err)); this.logger.error('searchPatientPromise 调用失败', { resourceType, searchParams, errMessage: error.message, errStack: error.stack }); // 抛出错误,让重试工具处理重试逻辑 throw error; } }
4. 修正getPatientData调用
传入请求函数而非已执行的Promise,确保重试时能重新发起请求:
async function getPatientData() { const logger: PinoLogger = new PinoLogger(); const retryCount = 3; // 传入请求函数,而非已执行的Promise实例 const patientRequest = () => this.searchPatientPromise(); const appointmentRequest = () => this.searchAppointmentPromise(); const practitionerRequest = () => this.searchPractitionerDetailsPromise(); const [patient, appointment, practitioner] = await resolvePromises( [patientRequest, appointmentRequest, practitionerRequest], retryCount, logger ); // 后续业务处理逻辑 }
关键修复效果
- 重试逻辑生效:每次重试都会重新发起第三方请求,而非复用旧Promise
- 所有错误都会被记录:异步错误被正确捕获,日志包含完整的错误详情和上下文
- 避免无响应:所有Promise都被正确处理,不会出现未捕获的拒绝导致应用挂起
- 代码结构简洁:通用工具函数复用性强,避免重复逻辑
内容的提问来源于stack exchange,提问作者AnMi
相关产品推荐
相关产品推荐

