下列代码证实了:无论是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 方法来实现更强大的并发控制。