JavaScript 实现 mergePromise 函数(前一个任务完成后再执行下一个)

4 阅读4分钟

要实现 mergePromise 函数,核心需求是 按数组顺序串行执行异步任务(前一个任务完成后再执行下一个),并将每个任务的返回结果按执行顺序存入 data 数组,最终返回包含该数组的 Promise。

以下是基于 Promise 链式调用(兼容所有环境)和 async/await(简洁优雅)的两种实现方案,均满足 “顺序执行、结果顺序存储” 的核心要求:

一、方案一:基于 Promise 链式调用(兼容 ES6+)

利用 Promise 的 then 链式特性,依次拼接任务执行逻辑,确保前一个任务完成后再执行下一个,同时收集结果。

/**
 * 按顺序串行执行异步任务,收集结果
 * @param {Function[]} tasks - 异步任务数组(每个任务是返回 Promise 的函数)
 * @returns {Promise<Array>} - 按执行顺序存储结果的数组
 */
function mergePromise(tasks) {
  // 边界校验:非数组或空数组直接返回空结果
  if (!Array.isArray(tasks)) {
    return Promise.reject(new Error('参数必须是任务数组'));
  }
  if (tasks.length === 0) {
    return Promise.resolve([]);
  }

  // 存储结果的数组
  const data = [];

  // 初始化 Promise 链(从一个已完成的 Promise 开始)
  return tasks.reduce((promiseChain, currentTask) => {
    // 前一个任务完成后,执行当前任务
    return promiseChain.then(() => {
      // 校验当前元素是否为可执行的异步任务(返回 Promise)
      if (typeof currentTask !== 'function' || !(currentTask() instanceof Promise)) {
        throw new Error('数组元素必须是返回 Promise 的函数');
      }
      // 执行当前任务,获取结果并存入数组
      return currentTask().then((result) => {
        data.push(result);
        return data; // 传递结果数组,供下一个 then 接收
      });
    });
  }, Promise.resolve()); // 初始 Promise:直接完成
}

二、方案二:基于 async/await(简洁优雅,推荐)

利用 async/await 的同步写法特性,更直观地实现 “顺序执行”,代码可读性更高。

/**
 * 按顺序串行执行异步任务,收集结果(async/await 版)
 * @param {Function[]} tasks - 异步任务数组(每个任务是返回 Promise 的函数)
 * @returns {Promise<Array>} - 按执行顺序存储结果的数组
 */
async function mergePromise(tasks) {
  // 边界校验
  if (!Array.isArray(tasks)) {
    throw new Error('参数必须是任务数组');
  }

  const data = [];
  for (const task of tasks) {
    // 校验当前元素是否为合法异步任务
    if (typeof task !== 'function' || !(task() instanceof Promise)) {
      throw new Error('数组元素必须是返回 Promise 的函数');
    }
    // 等待当前任务完成,再执行下一个(关键:串行)
    const result = await task();
    data.push(result); // 按执行顺序存入结果
  }

  return data; // 所有任务完成后,返回结果数组
}

三、核心逻辑说明

  1. 顺序执行保证

    • 方案一:通过 reduce 拼接 then 链,前一个 then 完成后才会执行下一个任务,天然串行;
    • 方案二:for...of 循环中用 await 等待当前任务完成,再进入下一次循环,直观串行。
  2. 结果顺序保证

    • 无论每个任务的执行耗时多久,data 数组的顺序始终与输入 tasks 数组的顺序一致(前一个任务的结果先存入)。
  3. 边界与合法性校验

    • 校验参数是否为数组,避免传入非数组导致报错;
    • 校验数组元素是否为 “返回 Promise 的函数”,确保任务可异步执行(若直接传入 Promise 对象,会导致所有任务同时启动,违背 “顺序执行” 要求)。

四、使用示例

// 1. 模拟异步任务(按顺序执行,每个任务延迟不同)
function createAsyncTask(taskId, delay = 1000) {
  return () => new Promise((resolve) => {
    setTimeout(() => {
      console.log(`任务 ${taskId} 执行完成`);
      resolve(`任务 ${taskId} 的结果`); // 任务返回结果
    }, delay);
  });
}

// 2. 构建任务数组(3个任务,延迟分别为 1s、2s、1s)
const tasks = [
  createAsyncTask(1, 1000),
  createAsyncTask(2, 2000),
  createAsyncTask(3, 1000)
];

// 3. 执行 mergePromise,获取结果
mergePromise(tasks)
  .then((data) => {
    console.log('所有任务执行完毕,结果数组:', data);
    // 输出:["任务 1 的结果", "任务 2 的结果", "任务 3 的结果"]
  })
  .catch((error) => {
    console.error('执行失败:', error.message);
  });

执行日志(按顺序输出):

任务 1 执行完成  // 1s 后
任务 2 执行完成  // 再等 2s(累计 3s)
任务 3 执行完成  // 再等 1s(累计 4s)
所有任务执行完毕,结果数组: ["任务 1 的结果", "任务 2 的结果", "任务 3 的结果"]

五、关键注意事项

  1. 任务必须是 “返回 Promise 的函数”

    • 错误示例:const tasks = [Promise.resolve(1), Promise.resolve(2)](会导致两个 Promise 同时执行,违背顺序要求);
    • 正确示例:const tasks = [() => Promise.resolve(1), () => Promise.resolve(2)](只有调用函数才会启动任务,确保顺序执行)。
  2. 错误处理

    • 单个任务失败会中断后续所有任务(符合串行逻辑:前一个任务失败,后续无需执行);
    • 若需 “失败不中断”,可在单个任务内部捕获错误(如 task().catch(err => { console.error(err); return null; })),确保 await 能正常继续。
  3. 空任务数组:直接返回 Promise.resolve([]),避免后续逻辑报错。

六、扩展:失败不中断的版本(可选)

如果需要单个任务失败后,继续执行后续任务(仅记录错误,不中断流程),可修改如下(以 async/await 版为例):

async function mergePromise(tasks) {
  if (!Array.isArray(tasks)) {
    throw new Error('参数必须是任务数组');
  }

  const data = [];
  for (const task of tasks) {
    try {
      if (typeof task !== 'function' || !(task() instanceof Promise)) {
        throw new Error('数组元素必须是返回 Promise 的函数');
      }
      const result = await task();
      data.push({ success: true, result });
    } catch (error) {
      data.push({ success: false, error: error.message });
    }
  }

  return data;
}

此时,失败的任务会在结果数组中标记错误状态,后续任务正常执行。