异步串行 | 异步并行
实现异步任务的串行执行和并行执行,掌握 Promise 控制流程的技巧
问题
在实际开发中,我们经常需要处理多个异步任务。根据业务场景不同,有时需要串行执行(一个接一个按顺序执行),有时需要并行执行(同时执行所有任务)。本题要求实现这两种异步任务的执行方式。
串行执行:任务按顺序依次执行,前一个任务完成后才执行下一个任务。 并行执行:所有任务同时开始执行,等待所有任务完成。
解答
/**
* 异步串行执行
* @param {Array<Function>} tasks - 异步任务数组,每个任务返回 Promise
* @returns {Promise<Array>} 返回所有任务结果的数组
*/
function serialExecute(tasks) {
return tasks.reduce((promiseChain, currentTask) => {
return promiseChain.then(results => {
return currentTask().then(result => {
return [...results, result];
});
});
}, Promise.resolve([]));
}
// 串行执行的另一种实现方式(使用 async/await)
async function serialExecuteAsync(tasks) {
const results = [];
for (const task of tasks) {
const result = await task();
results.push(result);
}
return results;
}
/**
* 异步并行执行
* @param {Array<Function>} tasks - 异步任务数组,每个任务返回 Promise
* @returns {Promise<Array>} 返回所有任务结果的数组
*/
function parallelExecute(tasks) {
return Promise.all(tasks.map(task => task()));
}
// 并行执行的另一种实现方式(手动实现 Promise.all)
function parallelExecuteManual(tasks) {
return new Promise((resolve, reject) => {
const results = [];
let completedCount = 0;
const totalTasks = tasks.length;
if (totalTasks === 0) {
resolve(results);
return;
}
tasks.forEach((task, index) => {
task()
.then(result => {
results[index] = result; // 保持结果顺序
completedCount++;
if (completedCount === totalTasks) {
resolve(results);
}
})
.catch(error => {
reject(error); // 任意一个失败就拒绝
});
});
});
}
使用示例
// 模拟异步任务
const createTask = (name, delay, shouldFail = false) => {
return () => {
return new Promise((resolve, reject) => {
console.log(`${name} 开始执行`);
setTimeout(() => {
if (shouldFail) {
console.log(`${name} 执行失败`);
reject(new Error(`${name} failed`));
} else {
console.log(`${name} 执行完成`);
resolve(`${name} 的结果`);
}
}, delay);
});
};
};
// 创建测试任务
const task1 = createTask('任务1', 1000);
const task2 = createTask('任务2', 500);
const task3 = createTask('任务3', 800);
// 测试串行执行
console.log('=== 串行执行测试 ===');
serialExecute([task1, task2, task3])
.then(results => {
console.log('串行执行结果:', results);
// 输出顺序:任务1 -> 任务2 -> 任务3
// 总耗时:约 2300ms (1000 + 500 + 800)
})
.catch(error => {
console.error('串行执行失败:', error);
});
// 测试并行执行
setTimeout(() => {
console.log('\n=== 并行执行测试 ===');
parallelExecute([task1, task2, task3])
.then(results => {
console.log('并行执行结果:', results);
// 所有任务同时开始
// 总耗时:约 1000ms (取最长的任务时间)
})
.catch(error => {
console.error('并行执行失败:', error);
});
}, 3000);
// 实际应用场景示例
async function fetchUserData() {
// 串行:必须先获取用户信息,再根据用户ID获取订单
const userTasks = [
() => fetch('/api/user').then(r => r.json()),
() => fetch('/api/orders').then(r => r.json())
];
const [user, orders] = await serialExecuteAsync(userTasks);
// 并行:同时获取多个独立的数据
const parallelTasks = [
() => fetch('/api/products').then(r => r.json()),
() => fetch('/api/categories').then(r => r.json()),
() => fetch('/api/banners').then(r => r.json())
];
const [products, categories, banners] = await parallelExecute(parallelTasks);
return { user, orders, products, categories, banners };
}
关键点
-
串行执行思路:使用
reduce构建 Promise 链,或使用async/await配合for...of循环,确保前一个任务完成后再执行下一个 -
并行执行思路:使用
Promise.all同时启动所有任务,等待全部完成;或手动实现计数器机制追踪完成状态 -
结果顺序保证:并行执行时要注意保持结果数组的顺序与任务数组一致,使用索引而非 push 来存储结果
-
错误处理:串行执行遇到错误会中断后续任务;并行执行任意一个失败会导致整体失败(可使用
Promise.allSettled改进) -
性能差异:串行执行时间是所有任务时间之和;并行执行时间取决于最慢的任务
-
应用场景:串行适用于有依赖关系的任务(如先登录再获取数据);并行适用于独立任务(如同时加载多个资源)
-
扩展方案:可实现带并发限制的并行执行(如最多同时执行 3 个任务),结合两者优势
目录