Promise.all 和 Promise.race 的区别
基本概念对比
| 特性 | Promise.all | Promise.race |
|---|---|---|
| 执行机制 | 等待所有Promise完成 | 只要有一个Promise完成就返回 |
| 返回值 | 所有Promise结果的数组 | 第一个完成的Promise结果 |
| 错误处理 | 有一个失败立即拒绝 | 第一个完成的(成功或失败) |
| 适用场景 | 并行处理,需要所有结果 | 竞速,超时控制,最快响应 |
详细解析
1. Promise.all
特点: 等待所有Promise完成,全部成功才算成功,有一个失败立即失败
javascript
// 基本用法
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);
});
成功案例:
javascript
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);
});
失败案例:
javascript
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先完成就返回哪个的结果
javascript
// 基本用法
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. 并行处理多个独立任务
javascript
// 同时获取用户信息和订单信息
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. 批量数据处理
javascript
// 批量上传文件
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. 请求超时控制
javascript
// 设置请求超时
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. 竞速获取资源
javascript
// 从多个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 => {
// 使用最快返回的数据
});
高级用法和区别演示
综合对比示例
javascript
// 创建测试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);
});
错误处理对比
javascript
// 包含错误的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
javascript
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
javascript
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. 基本行为演示
javascript
// 创建不同耗时的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 内部会维护结果的顺序:
javascript
// 手写实现,展示顺序保持原理
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:批量请求保持顺序
javascript
// 批量获取用户信息,需要保持返回顺序
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:文件处理保持顺序
javascript
// 批量处理文件,需要保持处理前后的顺序对应
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. 与执行完成顺序的对比
javascript
// 明确展示执行顺序 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. 错误情况下的顺序保证
javascript
// 即使有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 对比
javascript
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 对比
javascript
// 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请求后需要与请求参数对应
- 并行计算后需要与输入数据顺序对应