同时发起多个异步任务:方法与实践
摘要: 本文探讨了现代JavaScript中并发处理多个异步任务的方法。介绍了Promise.all(并行执行,任一失败即终止)、Promise.allSettled(等待所有任务完成,不中断)、Promise.race(仅取首个结果)等原生方案,并分析了其优缺点。针对动态控制需求,提出结合async/await与循环或并发控制的进阶实现,如分批次执行(for...of)和限制并发数(Promis

🤍 前端开发工程师、技术日更博主、已过CET6
🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1
🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》
🍚 蓝桥云课签约作者、上架课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入门到实战全面掌握 uni-app》
文章目录
在现代 JavaScript 开发中,异步编程是处理 I/O 操作(如网络请求、文件读写等)的常见方式。然而,当需要同时发起多个异步任务时,如何高效地管理这些任务并确保它们正确完成,是一个常见的挑战。本文将详细介绍如何同时发起多个异步任务,并探讨不同的实现方法及其适用场景。
一、异步任务的常见场景
在实际开发中,我们经常需要同时处理多个异步任务。以下是一些常见的场景:
- 并发请求:同时向多个 API 发起请求,获取数据后进行后续处理。
- 并行处理:对多个数据项进行异步处理,例如批量上传文件或批量处理数据。
- 任务组合:多个异步任务相互依赖,需要在所有任务完成后执行后续操作。
二、实现方法
(一)Promise.all
Promise.all 是处理多个异步任务的最常用方法之一。它接受一个 Promise 数组作为参数,当所有 Promise 都成功完成时,返回一个新的 Promise,其结果是一个包含所有 Promise 结果的数组。
示例代码
function fetchUser(id) {
return fetch(`https://api.example.com/users/${id}`).then((res) => res.json());
}
const userIds = [1, 2, 3];
const promises = userIds.map((id) => fetchUser(id));
Promise.all(promises)
.then((results) => {
console.log(results); // [user1, user2, user3]
})
.catch((error) => {
console.error("One of the requests failed:", error);
});
特点
- 优点:
- 简单易用,适合同时处理多个独立的异步任务。
- 所有任务并行执行,效率高。
- 缺点:
- 如果任何一个
Promise失败,整个Promise.all会立即失败,后续的Promise也会被中断。 - 无法动态控制并发数量。
- 如果任何一个
(二)Promise.allSettled
Promise.allSettled 是 Promise.all 的一个变体,它不会因为某个 Promise 的失败而中断整个操作。它会等待所有 Promise 都完成(无论是成功还是失败),并返回一个包含每个 Promise 状态和结果的数组。
示例代码
const promises = [
Promise.resolve(1),
Promise.reject(new Error("Error")),
Promise.resolve(3),
];
Promise.allSettled(promises)
.then((results) => {
console.log(results);
// [
// { status: "fulfilled", value: 1 },
// { status: "rejected", reason: Error: Error },
// { status: "fulfilled", value: 3 }
// ]
});
特点
- 优点:
- 不会被任何一个
Promise的失败中断,可以处理所有任务的结果。
- 不会被任何一个
- 缺点:
- 无法提前终止失败的任务。
- 仍然无法动态控制并发数量。
(三)Promise.race
Promise.race 用于处理多个异步任务,但它只会返回第一个完成的 Promise 的结果,其他 Promise 的结果会被忽略。
示例代码
const promises = [
new Promise((resolve) => setTimeout(() => resolve("First"), 1000)),
new Promise((resolve) => setTimeout(() => resolve("Second"), 500)),
];
Promise.race(promises)
.then((result) => {
console.log(result); // "Second"
})
.catch((error) => {
console.error(error);
});
特点
- 优点:
- 可以快速获取第一个完成的任务结果。
- 缺点:
- 只处理第一个完成的任务,其他任务的结果会被忽略。
- 不适合需要处理多个任务的场景。
(四)async/await + for...of 循环
如果需要动态控制并发数量,可以结合 async/await 和 for...of 循环逐个处理异步任务。这种方法可以更好地控制任务的执行顺序和并发数量。
示例代码
async function fetchUsers(userIds) {
const results = [];
for (const id of userIds) {
try {
const user = await fetchUser(id);
results.push(user);
} catch (error) {
console.error(`Failed to fetch user ${id}:`, error);
}
}
return results;
}
const userIds = [1, 2, 3];
fetchUsers(userIds).then((results) => {
console.log(results); // [user1, user2, user3]
});
特点
- 优点:
- 可以动态控制任务的执行顺序和并发数量。
- 可以逐个处理失败的任务,不会中断整个操作。
- 缺点:
- 任务是顺序执行的,效率较低。
(五)并发控制:async/await + Promise.all
如果需要同时发起多个异步任务,但又希望控制并发数量,可以结合 async/await 和 Promise.all。这种方法可以动态控制并发数量,同时保持任务的并行执行。
示例代码
async function fetchUsersWithConcurrency(userIds, concurrency) {
const results = [];
const executing = [];
for (const id of userIds) {
const promise = fetchUser(id).then((user) => {
results.push(user);
});
executing.push(promise);
if (executing.length >= concurrency) {
await Promise.race(executing);
executing.splice(executing.indexOf(Promise.race(executing)), 1);
}
}
await Promise.all(executing);
return results;
}
const userIds = [1, 2, 3, 4, 5];
fetchUsersWithConcurrency(userIds, 3).then((results) => {
console.log(results); // [user1, user2, user3, user4, user5]
});
特点
- 优点:
- 可以动态控制并发数量,同时保持任务的并行执行。
- 适合处理大量异步任务,避免因并发过多导致的性能问题。
- 缺点:
- 实现相对复杂,需要手动管理并发任务。
三、选择合适的方法
在选择同时发起多个异步任务的方法时,需要根据具体需求和场景进行选择:
Promise.all:适用于同时处理多个独立的异步任务,任务之间没有依赖关系。Promise.allSettled:适用于需要处理所有任务的结果,即使某些任务失败。Promise.race:适用于只需要第一个完成的任务结果。async/await+for...of循环:适用于需要动态控制任务执行顺序和并发数量。- 并发控制:
async/await+Promise.all:适用于需要控制并发数量,同时保持任务并行执行。
四、总结
同时发起多个异步任务是现代 JavaScript 开发中的常见需求。通过合理使用 Promise.all、Promise.allSettled、Promise.race、async/await 和并发控制方法,可以高效地管理多个异步任务,并确保它们正确完成。
在实际开发中,开发者需要根据具体需求选择合适的方法。例如,如果任务之间没有依赖关系且需要快速完成,可以选择 Promise.all;如果需要处理所有任务的结果,可以选择 Promise.allSettled;如果需要动态控制并发数量,可以选择 async/await 结合并发控制逻辑。
通过掌握这些方法,开发者可以更好地处理异步任务,提高代码的效率和可维护性。
更多推荐



所有评论(0)