You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js/TypeScript中Promise.allSettled的重试与失败日志最佳实践

Promise失败日志记录与重试逻辑优化方案

问题核心

调用第三方服务的多个Promise请求偶尔导致应用无响应,调用方超时;已添加日志但无输出,重试逻辑无效,需要解决:

  • 正确记录Promise失败的方式
  • 最优整合重试、日志记录、3次重试失败后返回null的逻辑
  • 修复无日志、无响应的问题

现有代码的关键问题

  1. 重试逻辑无效:promiseRetry传入的是已执行的Promise实例,重试时复用同一个已完成(pending/resolved/rejected)的Promise,不会重新发起请求
  2. 异步错误未捕获:searchPatientPromise中externalService.search()是异步操作但未加await,同步try/catch无法捕获Promise拒绝错误,导致无日志
  3. 日志信息错误:getSettledPromiseValue参数名错误(传入retryCount却用encounterId字段),日志定位信息不准确
  4. 冗余处理: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 13:04:54