实现带重试功能的 Promise

问题描述

请完成 retryRequest 函数的实现。该函数接收一个异步任务函数 requestFn 和一个配置对象 options,并返回一个 PromiseretryRequest 会尝试执行 requestFn,如果 requestFn 返回的 Promise 被拒绝(rejected),它会根据配置进行重试。

函数签名:

function retryRequest(requestFn, options = {})

参数说明

  • requestFn: 一个返回 Promise 的函数。这是需要被执行并可能需要重试的异步任务。
  • options (可选): 一个配置对象,包含以下属性:
    • maxRetries (number): 最大重试次数。默认为 3。这意味着除了首次尝试之外,最多还会再尝试 3 次。
    • retryDelay (number): 每次重试之间的延迟时间(单位:毫秒)。默认为 1000
    • onRetry (function): 一个回调函数,在每次重试尝试之前被调用。它会接收两个参数:error (导致失败的错误) 和 attempt (当前的重试次数,从 1 开始)。

返回值

该函数返回一个 Promise

  • 成功时:如果 requestFn 在某次尝试中成功 resolve,则 retryRequest 返回的 Promise 也应 resolve,并传递 requestFn 的解析值。
  • 失败时:如果所有尝试(包括首次尝试和所有重试)都失败了,则 retryRequest 返回的 Promisereject,并传递最后一次尝试所产生的错误。

示例

// 一个模拟 API 请求,它将在第二次重试时成功
let attemptCount = 0;
const flakyRequest = () => {
  return new Promise((resolve, reject) => {
    attemptCount++;
    console.log(`发起第 ${attemptCount} 次请求...`);
    if (attemptCount < 3) {
      reject(new Error(`请求失败`));
    } else {
      resolve('成功获取数据!');
    }
  });
};

retryRequest(flakyRequest, {
  maxRetries: 5,
  retryDelay: 500,
  onRetry: (error, attempt) => {
    console.warn(`捕获到错误: ${error.message}。这是第 ${attempt} 次重试。`);
  },
})
  .then((result) => {
    console.log(`最终结果: ${result}`);
  })
  .catch((error) => {
    console.error(`所有重试均失败: ${error.message}`);
  });

// ---- 预期输出 ----
// 发起第 1 次请求...
// 捕获到错误: 请求失败。这是第 1 次重试。
// (等待 500ms)
// 发起第 2 次请求...
// 捕获到错误: 请求失败。这是第 2 次重试。
// (等待 500ms)
// 发起第 3 次请求...
// 最终结果: 成功获取数据!

解题思路

解决这个问题的最佳方法是使用 async/awaitfor 循环,这能让异步的重试逻辑变得像同步代码一样清晰易读。

  1. 将函数声明为 async: 为了在函数内部直接使用 await,我们需要将 retryRequest 声明为一个 async function。这样,函数将隐式地返回一个 Promise

  2. 循环处理所有尝试: 使用一个 for 循环来控制总的尝试次数。总次数应该是 1 (首次尝试) + maxRetries (最大重试次数)。循环可以从 0maxRetries

  3. 使用 try...catch: 在循环内部,使用 try...catch 块来包裹对 requestFn 的调用。

    • try: 在这里 await requestFn()。如果这个 Promise 成功 resolve,说明任务成功了,我们直接 return 结果。这个 returnresolve 整个 async 函数返回的 Promise,流程结束。
    • catch: 如果 requestFn() 失败并抛出错误,catch 块会被执行。在这里我们处理重试逻辑。
  4. 处理重试逻辑:

    • 检查是否为最后一次尝试: 在 catch 块中,首先检查当前是否是最后一次允许的尝试(即 attempt === maxRetries)。如果是,说明所有重试机会都已用尽,我们应该通过 throw errorreject 整个 async 函数的 Promise
    • 执行 onRetry 回调: 如果还有重试机会,检查并调用 onRetry 回调函数,向其传递当前的错误和尝试次数。
    • 实现延迟: 使用 new Promise(resolve => setTimeout(resolve, retryDelay)) 创建一个延时 Promise,并 await 它。这样可以暂停执行,等待指定的 retryDelay 时间后再进入下一次循环。

通过这个结构,代码可以清晰地表达“尝试 -> 失败 -> 等待 -> 再次尝试”的循环,直到成功或次数耗尽为止。

代码实现

/**
 * 实现一个基于Promise的请求重试机制。
 * @param {() => Promise<any>} requestFn - 一个返回 Promise 的函数,代表需要执行的异步任务。
 * @param {object} [options={}] - 可选的配置对象。
 * @param {number} [options.maxRetries=3] - 最大重试次数。
 * @param {number} [options.retryDelay=1000] - 每次重试之间的延迟(毫秒)。
 * @param {(error: any, attempt: number) => void} [options.onRetry=null] - 每次重试前调用的回调函数。
 * @returns {Promise<any>}
 */
async function retryRequest(requestFn, options = {}) {
  const { maxRetries = 3, retryDelay = 1000, onRetry = null } = options;

  // 循环处理所有尝试,从首次尝试(i=0)到最后一次重试(i=maxRetries)
  for (let i = 0; i <= maxRetries; i++) {
    try {
      // 尝试执行请求
      const result = await requestFn();
      // 如果成功,立即返回结果,Promise 将会 resolve
      return result;
    } catch (error) {
      // 如果这是最后一次尝试,则抛出错误,Promise 将会 reject
      if (i === maxRetries) {
        throw error;
      }

      // 如果 onRetry 回调存在,则调用它
      if (onRetry && typeof onRetry === 'function') {
        // 传递的 attempt 是从 1 开始的
        onRetry(error, i + 1);
      }

      // 等待指定的延迟时间
      if (retryDelay > 0) {
        await new Promise((resolve) => setTimeout(resolve, retryDelay));
      }
    }
  }
}

// --- 测试用例 ---

// 1. 测试在第二次重试时成功
let attemptCount1 = 0;
const requestThatSucceeds = () =>
  new Promise((resolve, reject) => {
    attemptCount1++;
    console.log(`[测试1] 发起第 ${attemptCount1} 次请求...`);
    if (attemptCount1 < 3) {
      reject(new Error(`请求失败`));
    } else {
      resolve('成功获取数据!');
    }
  });

console.log('--- 开始测试用例 1: 重试后成功 ---');
retryRequest(requestThatSucceeds, {
  maxRetries: 5,
  retryDelay: 200,
  onRetry: (error, attempt) => {
    console.warn(
      `[测试1] 捕获到错误: ${error.message}。第 ${attempt} 次重试...`
    );
  },
})
  .then((result) => console.log(`[测试1] 最终结果: ${result}\n`))
  .catch((error) => console.error(`[测试1] 最终失败: ${error.message}\n`));

// 2. 测试所有重试都失败
let attemptCount2 = 0;
const requestThatFails = () =>
  new Promise((resolve, reject) => {
    attemptCount2++;
    console.log(`[测试2] 发起第 ${attemptCount2} 次请求...`);
    reject(new Error(`永久性错误`));
  });

// 使用.then()来等待上一个测试完成
new Promise((r) => setTimeout(r, 1000)).then(() => {
  console.log('--- 开始测试用例 2: 所有重试均失败 ---');
  retryRequest(requestThatFails, {
    maxRetries: 2,
    retryDelay: 200,
  })
    .then((result) => console.log(`[测试2] 最终结果: ${result}\n`))
    .catch((error) => console.error(`[测试2] 最终失败: ${error.message}\n`));
});
Copyright © Jun 2025 all right reserved,powered by Gitbook该文件修订时间: 2025-07-03 17:35:08

results matching ""

    No results matching ""

    results matching ""

      No results matching ""