Promise.all和Promise.race的区别
Promise.all 和 Promise.race 的区别
基本概念对比
| 特性 | Promise.all | Promise.race |
|---|---|---|
| 执行机制 | 等待所有Promise完成 | 只要有一个Promise完成就返回 |
| 返回值 | 所有Promise结果的数组 | 第一个完成的Promise结果 |
| 错误处理 | 有一个失败立即拒绝 | 第一个完成的(成功或失败) |
| 适用场景 | 并行处理,需要所有结果 | 竞速,超时控制,最快响应 |
详细解析
1. Promise.all
特点: 等待所有Promise完成,全部成功才算成功,有一个失败立即失败
// 基本用法
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);Promise.all([promise1, promise2, promise3]).then(results => {console.log(results); // [1, 2, 3]}).catch(error => {console.error('有一个Promise失败了:', error);});
成功案例:
const urls = ['/api/user', '/api/posts', '/api/settings'];// 并行请求多个接口
Promise.all(urls.map(url => fetch(url))).then(responses => {// 所有请求都成功完成return Promise.all(responses.map(response => response.json()));}).then(data => {console.log('用户数据:', data[0]);console.log('文章数据:', data[1]);console.log('设置数据:', data[2]);}).catch(error => {console.error('某个请求失败了:', error);});
失败案例:
const promise1 = Promise.resolve('成功1');
const promise2 = Promise.reject('失败啦!');
const promise3 = Promise.resolve('成功3');Promise.all([promise1, promise2, promise3]).then(results => {console.log(results); // 不会执行到这里}).catch(error => {console.error('捕获到错误:', error); // "失败啦!"});
2. Promise.race
特点: 竞速模式,哪个Promise先完成就返回哪个的结果
// 基本用法
const promise1 = new Promise((resolve) => {setTimeout(() => resolve('第一个完成'), 1000);
});const promise2 = new Promise((resolve) => {setTimeout(() => resolve('第二个完成'), 500);
});Promise.race([promise1, promise2]).then(result => {console.log(result); // "第二个完成" (因为500ms比1000ms快)}).catch(error => {console.error('第一个完成的Promise失败了:', error);});
实际应用场景
Promise.all 应用场景
1. 并行处理多个独立任务
// 同时获取用户信息和订单信息
function getUserDashboard(userId) {const userPromise = fetch(`/api/users/${userId}`);const ordersPromise = fetch(`/api/users/${userId}/orders`);const settingsPromise = fetch(`/api/users/${userId}/settings`);return Promise.all([userPromise, ordersPromise, settingsPromise]).then(responses => Promise.all(responses.map(r => r.json()))).then(([user, orders, settings]) => {return {user,orders,settings};});
}
2. 批量数据处理
// 批量上传文件
function uploadFiles(files) {const uploadPromises = files.map(file => {return new Promise((resolve, reject) => {const formData = new FormData();formData.append('file', file);fetch('/api/upload', {method: 'POST',body: formData}).then(response => response.json()).then(resolve).catch(reject);});});return Promise.all(uploadPromises).then(results => {console.log('所有文件上传完成:', results);return results;});
}
Promise.race 应用场景
1. 请求超时控制
// 设置请求超时
function fetchWithTimeout(url, timeout = 5000) {const fetchPromise = fetch(url);const timeoutPromise = new Promise((_, reject) => {setTimeout(() => reject(new Error('请求超时')), timeout);});return Promise.race([fetchPromise, timeoutPromise]);
}// 使用示例
fetchWithTimeout('/api/data', 3000).then(response => response.json()).then(data => console.log('数据:', data)).catch(error => {if (error.message === '请求超时') {console.log('请求超时,显示备用数据');} else {console.error('其他错误:', error);}});
2. 竞速获取资源
// 从多个CDN源获取资源,使用最快的响应
function getFastestResource(resourceUrls) {const fetchPromises = resourceUrls.map(url => fetch(url).then(response => response.json()));return Promise.race(fetchPromises).then(data => {console.log('使用最快响应的数据源');return data;});
}// 使用多个CDN源
const cdnUrls = ['https://cdn1.example.com/data.json','https://cdn2.example.com/data.json','https://cdn3.example.com/data.json'
];getFastestResource(cdnUrls).then(data => {// 使用最快返回的数据
});
高级用法和区别演示
综合对比示例
// 创建测试Promise
const createPromise = (value, delay, shouldReject = false) => {return new Promise((resolve, reject) => {setTimeout(() => {if (shouldReject) {reject(`错误: ${value}`);} else {resolve(`成功: ${value}`);}}, delay);});
};const p1 = createPromise('A', 1000);
const p2 = createPromise('B', 500);
const p3 = createPromise('C', 2000);console.log('=== Promise.all 测试 ===');
Promise.all([p1, p2, p3]).then(results => {console.log('Promise.all 结果:', results);// ["成功: A", "成功: B", "成功: C"] (等待所有完成)}).catch(error => {console.log('Promise.all 错误:', error);});console.log('=== Promise.race 测试 ===');
Promise.race([p1, p2, p3]).then(result => {console.log('Promise.race 结果:', result);// "成功: B" (最快完成的)}).catch(error => {console.log('Promise.race 错误:', error);});
错误处理对比
// 包含错误的Promise测试
const successPromise = createPromise('成功', 1000);
const errorPromise = createPromise('错误', 500, true);
const latePromise = createPromise('迟到的', 2000);console.log('=== Promise.all 错误处理 ===');
Promise.all([successPromise, errorPromise, latePromise]).then(results => {console.log('不会执行这里');}).catch(error => {console.log('Promise.all 捕获错误:', error); // "错误: 错误"// 立即失败,不会等待其他Promise});console.log('=== Promise.race 错误处理 ===');
Promise.race([successPromise, errorPromise, latePromise]).then(result => {console.log('Promise.race 成功:', result);}).catch(error => {console.log('Promise.race 捕获错误:', error); // "错误: 错误"// 第一个完成的(无论成功失败)});
手写实现
手写 Promise.all
Promise.myAll = function(promises) {return new Promise((resolve, reject) => {if (!Array.isArray(promises)) {return reject(new TypeError('参数必须是数组'));}const results = [];let completedCount = 0;if (promises.length === 0) {return resolve(results);}promises.forEach((promise, index) => {Promise.resolve(promise).then(value => {results[index] = value;completedCount++;if (completedCount === promises.length) {resolve(results);}}).catch(reject); // 任何一个失败立即拒绝});});
};
手写 Promise.race
Promise.myRace = function(promises) {return new Promise((resolve, reject) => {if (!Array.isArray(promises)) {return reject(new TypeError('参数必须是数组'));}if (promises.length === 0) {return; // 永远pending}promises.forEach(promise => {Promise.resolve(promise).then(resolve) // 第一个成功的.catch(reject); // 第一个失败的});});
};
总结表格
| 方面 | Promise.all | Promise.race |
|---|---|---|
| 执行策略 | 全部完成 | 竞速完成 |
| 返回值 | 结果数组 | 单个结果 |
| 成功条件 | 全部成功 | 第一个成功 |
| 失败条件 | 任一失败 | 第一个失败 |
| 等待时间 | 最慢的完成时间 | 最快的完成时间 |
| 内存占用 | 保存所有结果 | 只保存一个结果 |
| 典型用途 | 并行计算、批量操作 | 超时控制、竞速请求 |
使用建议
-
使用 Promise.all 当:
- 需要所有异步操作的结果
- 操作之间相互独立
- 可以容忍最慢的操作时间
-
使用 Promise.race 当:
- 只需要最快的结果
- 实现超时机制
- 多个备用方案竞争
-
注意事项:
- Promise.all 中一个失败会导致整个失败
- Promise.race 不保证结果的稳定性
- 都要做好错误处理
Promise.all 返回顺序与执行顺序的关系
结论
Promise.all 返回结果的顺序与传入的Promise数组顺序完全一致,与执行完成的先后顺序无关。
详细解析
1. 基本行为演示
// 创建不同耗时的Promise
const createPromise = (value, delay) => {return new Promise(resolve => {setTimeout(() => {console.log(`完成: ${value}, 耗时: ${delay}ms`);resolve(value);}, delay);});
};const promise1 = createPromise('第一个', 1000); // 最慢
const promise2 = createPromise('第二个', 300); // 最快
const promise3 = createPromise('第三个', 500); // 中等Promise.all([promise1, promise2, promise3]).then(results => {console.log('最终结果顺序:', results);// 输出: ["第一个", "第二个", "第三个"]// 尽管执行完成顺序是: 第二个 -> 第三个 -> 第一个});// 控制台输出:
// 完成: 第二个, 耗时: 300ms
// 完成: 第三个, 耗时: 500ms
// 完成: 第一个, 耗时: 1000ms
// 最终结果顺序: ["第一个", "第二个", "第三个"]
2. 实现原理
Promise.all 内部会维护结果的顺序:
// 手写实现,展示顺序保持原理
Promise.myAll = function(promises) {return new Promise((resolve, reject) => {const results = new Array(promises.length);let completedCount = 0;promises.forEach((promise, index) => {Promise.resolve(promise).then(value => {// 关键:根据原始索引存储结果results[index] = value;completedCount++;if (completedCount === promises.length) {resolve(results); // 按原始顺序返回}}).catch(reject);});});
};
3. 实际应用场景
场景1:批量请求保持顺序
// 批量获取用户信息,需要保持返回顺序
const userIds = [1, 2, 3, 4, 5];function getUserInfo(userId) {return fetch(`/api/users/${userId}`).then(response => response.json()).then(user => {console.log(`获取到用户 ${userId} 的数据`);return user;});
}// 尽管网络请求完成时间不同,但结果顺序与 userIds 一致
Promise.all(userIds.map(getUserInfo)).then(users => {// users[0] 对应 userIds[0] 的用户// users[1] 对应 userIds[1] 的用户// 以此类推...console.log('用户顺序保持不变:', users.map(user => user.id));// 输出: [1, 2, 3, 4, 5]});
场景2:文件处理保持顺序
// 批量处理文件,需要保持处理前后的顺序对应
const files = ['file1.txt', 'file2.txt', 'file3.txt'];function processFile(filename, index) {return new Promise(resolve => {const processTime = Math.random() * 1000 + 500; // 随机处理时间setTimeout(() => {console.log(`处理完成: ${filename}, 索引: ${index}`);resolve({originalName: filename,processedName: `processed_${filename}`,index: index});}, processTime);});
}Promise.all(files.map((file, index) => processFile(file, index))).then(processedFiles => {console.log('最终结果顺序:');processedFiles.forEach((file, index) => {console.log(`结果[${index}]: ${file.originalName} -> ${file.processedName}`);});// 尽管处理完成顺序随机,但最终数组顺序与 files 数组一致});
4. 与执行完成顺序的对比
// 明确展示执行顺序 vs 返回顺序
const tasks = [{ name: '任务A', time: 2000 },{ name: '任务B', time: 500 },{ name: '任务C', time: 1000 },{ name: '任务D', time: 300 }
];const promises = tasks.map((task, index) => {return new Promise(resolve => {setTimeout(() => {const completionInfo = {taskName: task.name,originalIndex: index,completedAt: new Date().toISOString()};console.log(`✅ 完成: ${task.name} (原索引: ${index})`);resolve(completionInfo);}, task.time);});
});console.log('开始执行Promise.all...');
console.log('预期返回顺序:', tasks.map(t => t.name));Promise.all(promises).then(results => {console.log('\n📋 最终返回顺序:');results.forEach((result, index) => {console.log(`结果[${index}]: ${result.taskName} (原索引: ${result.originalIndex})`);});console.log('\n⏱️ 实际完成顺序:');const sortedByCompletion = [...results].sort((a, b) => new Date(a.completedAt) - new Date(b.completedAt));sortedByCompletion.forEach((result, index) => {console.log(`第${index + 1}个完成: ${result.taskName}`);});});// 输出示例:
// 开始执行Promise.all...
// 预期返回顺序: ["任务A", "任务B", "任务C", "任务D"]
// ✅ 完成: 任务D (原索引: 3)
// ✅ 完成: 任务B (原索引: 1)
// ✅ 完成: 任务C (原索引: 2)
// ✅ 完成: 任务A (原索引: 0)
//
// 📋 最终返回顺序:
// 结果[0]: 任务A (原索引: 0)
// 结果[1]: 任务B (原索引: 1)
// 结果[2]: 任务C (原索引: 2)
// 结果[3]: 任务D (原索引: 3)
//
// ⏱️ 实际完成顺序:
// 第1个完成: 任务D
// 第2个完成: 任务B
// 第3个完成: 任务C
// 第4个完成: 任务A
5. 错误情况下的顺序保证
// 即使有Promise失败,已完成的结果顺序仍然保持
const mixedPromises = [Promise.resolve('成功1'),Promise.reject('错误!'),Promise.resolve('成功3'),Promise.resolve('成功4')
];// 在第二个Promise失败前,第一个已经成功,但结果不会包含在错误中
Promise.all(mixedPromises).then(results => {console.log('成功结果:', results);}).catch(error => {console.log('捕获错误:', error); // "错误!"// 注意:即使第一个Promise已经成功完成,我们也不会看到它的结果// 整个Promise.all立即拒绝,不等待其他Promise});
6. 与其他方法的对比
与 Promise.race 对比
const promises = [new Promise(resolve => setTimeout(() => resolve('慢的'), 1000)),new Promise(resolve => setTimeout(() => resolve('快的'), 100))
];// Promise.all - 保持顺序
Promise.all(promises).then(results => {console.log('Promise.all 结果:', results); // ["慢的", "快的"]
});// Promise.race - 只取最快
Promise.race(promises).then(result => {console.log('Promise.race 结果:', result); // "快的"
});
与 Promise.allSettled 对比
// Promise.allSettled 同样保持顺序
const promisesWithErrors = [Promise.resolve('成功1'),Promise.reject('错误2'),Promise.resolve('成功3')
];Promise.allSettled(promisesWithErrors).then(results => {console.log('allSettled 结果顺序:');results.forEach((result, index) => {console.log(`[${index}]:`, result.status, result.value || result.reason);});// 顺序保持: 成功1, 错误2, 成功3});
重要总结
- 顺序保证: Promise.all 严格按传入数组顺序返回结果
- 索引对应: 结果数组的索引与原始Promise数组索引一一对应
- 与执行时间无关: 无论哪个Promise先完成,返回顺序不变
- 错误处理: 一旦有Promise失败,立即拒绝,不返回任何成功结果
- 适用场景: 需要保持处理前后顺序对应的批量异步操作
这种顺序保证的特性使得 Promise.all 在处理需要保持对应关系的批量操作时非常有用,比如:
- 批量文件上传后需要与原文件列表对应
- 多个API请求后需要与请求参数对应
- 并行计算后需要与输入数据顺序对应
