要实现 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; // 所有任务完成后,返回结果数组
}
三、核心逻辑说明
-
顺序执行保证:
- 方案一:通过
reduce拼接then链,前一个then完成后才会执行下一个任务,天然串行; - 方案二:
for...of循环中用await等待当前任务完成,再进入下一次循环,直观串行。
- 方案一:通过
-
结果顺序保证:
- 无论每个任务的执行耗时多久,
data数组的顺序始终与输入tasks数组的顺序一致(前一个任务的结果先存入)。
- 无论每个任务的执行耗时多久,
-
边界与合法性校验:
- 校验参数是否为数组,避免传入非数组导致报错;
- 校验数组元素是否为 “返回 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 的结果"]
五、关键注意事项
-
任务必须是 “返回 Promise 的函数” :
- 错误示例:
const tasks = [Promise.resolve(1), Promise.resolve(2)](会导致两个 Promise 同时执行,违背顺序要求); - 正确示例:
const tasks = [() => Promise.resolve(1), () => Promise.resolve(2)](只有调用函数才会启动任务,确保顺序执行)。
- 错误示例:
-
错误处理:
- 单个任务失败会中断后续所有任务(符合串行逻辑:前一个任务失败,后续无需执行);
- 若需 “失败不中断”,可在单个任务内部捕获错误(如
task().catch(err => { console.error(err); return null; })),确保await能正常继续。
-
空任务数组:直接返回
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;
}
此时,失败的任务会在结果数组中标记错误状态,后续任务正常执行。