Promise的allSettled,all,race
下列代码证实了:无论是for或是for of循环, 都会等上一个请求彻底完成,才会开始下一个
// 模拟一个获取用户数据的api请求
function fetchUser(id){
return new Promise(resolve=>{
setTimeout(() =>{
console.log(`获取到用户${id}`);// 模拟网络请求resolve({id: id,name:`用户${id}`});},1000);// 假设每个请求需要1秒钟});
}// 错误做法:在循环里一个接一个地等
async function getAllUsers(userIds){
console.time('获取所有用户耗时');
const users = [];
//无论是for或是for of,都会等上一个请求彻底完成,才会开始下一个
// for(const id of userIds) {
// // 关键问题:这里会停下来等,等上一个请求彻底完成,才会开始下一个
// const user =await fetchUser(id);
// users.push(user);
// }for(let i=0;i<userIds.length;i++){const user =await fetchUser(userIds[i]);users.push(user);}
console.timeEnd('获取所有用户耗时');
return users;
}const userIds = [1,2,3,4,5];
getAllUsers(userIds);
// 控制台输出:获取所有用户耗时: 约5000毫秒
Promise.race: 只要数组里有一个Promise完成(无论是成功还是失败),它就立刻完成,结果或错误就是那个最快的Promise的。
适合做超时控制或者从多个来源取最快响应(比如测哪个CDN快)。
async function getFirstResponse(){const timeoutPromise =new Promise((_, reject) =>setTimeout(() =>reject(new Error('超时!')),500));const dataPromise = new Promise((resolve, _) =>setTimeout(() =>resolve(123),1500));try{const result =await Promise.race([dataPromise, timeoutPromise]);console.log('成功获取数据:', result);}catch(error) {console.log('出错或超时:', error);}
}
getFirstResponse()
Promise.any: 等待第一个成功完成的Promise。只有数组里所有的Promise都失败了,它才失败。适合需要尝试多个途径,只要有一个成功就行。
async function getFromAnySource(sources) {
try {
const firstSuccess = await Promise.any(sources.map(source => fetch(source)));
console.log('从最快成功的源获取:', firstSuccess);} catch (errors) { // 注意:错误是 AggregateError
console.log('所有源都失败了:', errors);}
}
Promise.allSettled:每个都要结果,不管成功失败
如果有些请求可能会失败,但你不想让一个失败就中断所有,还想知道每个请求最终是成功还是失败了,用Promise.allSettled。
本例中,fetch后面都用catch接住,所以allSettled里的结果都视作成功的结果
const apiRequests = [fetch("https://api.example.com/data1").catch(e=>e),fetch("https://api.example.com/data2").catch(e=>e),fetch("https://api.example.com/data3").catch(e=>e),
];Promise.allSettled(apiRequests).then(results => {const successfulData = results.filter(result => result.status === "fulfilled").map(result => result.value);const errors = results.filter(result => result.status === "rejected").map(result => result.reason);console.log("Successful responses:", successfulData);console.log("Errors:", errors[0]);});
控制同时请求的数量:别把服务器压垮
如果你的用户ID列表有1000个,用Promise.all会瞬间发出1000个请求。
这可能会让你的服务器崩溃,或者被浏览器限制(浏览器通常对同一域名有并发请求数限制,比如6-8个)。
这时候你需要一个“池子”来控制同时进行的请求数量。这里提供一个简单但有效的实现方法:
async function runWithConcurrency(tasks, maxConcurrent) {
const results = []; // 存放所有任务的最终结果(Promise)
const activeTasks = []; // 当前正在执行的任务对应的Promise(用于跟踪)for (const task of tasks) {
// 1. 创建代表当前任务的Promise。`() => task()` 确保任务在需要时才启动
const taskPromise = Promise.resolve().then(task);results.push(taskPromise); // 保存结果,最后统一用 Promise.all 等// 2. 创建任务完成后的清理操作:从 activeTasks 中移除自己
const removeFromActive = () => activeTasks.splice(activeTasks.indexOf(removeFromActive), 1);activeTasks.push(removeFromActive); // 注意:这里存的是清理函数对应的Promise// 3. 如果当前活跃任务数已达上限,就等任意一个完成
if (activeTasks.length >= maxConcurrent) {
await Promise.race(activeTasks); // 等 activeTasks 数组里任意一个Promise完成}// 4. 将清理操作与实际任务完成挂钩taskPromise.then(removeFromActive, removeFromActive); // 无论成功失败都清理}// 5. 等待所有任务完成(无论是否在活跃池中)
return Promise.allSettled(results); // 或者用 Promise.all(results) 只关心成功
}// 使用示例
const userIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 将 fetchUser(id) 调用包装成无参数的函数数组
const tasks = userIds.map(id =>() => fetchUser(id));// 最多同时发出 3 个请求
runWithConcurrency(tasks, 3).then(results => {
console.log('所有用户获取完成 (并发控制):', results);
});
这个函数会确保最多只有maxConcurrent个请求同时在进行。
当一个请求完成,池子里有空位了,才会开始下一个请求。在实际项目中,你也可以使用成熟的库如 p-limit 或 async 的 queue 方法来实现更强大的并发控制。