Promise组合
Promise组合
Promise提供了多种静态方法用于组合和管理多个Promise。本文将介绍Promise.all()、Promise.race()、Promise.allSettled()和Promise.any()等方法的用法和应用场景。
Promise.all()
Promise.all()
方法接收一个 Promise 数组作为输入,并返回一个新的 Promise,该 Promise 在所有输入的 Promise 都成功时才会成功,或者在任何一个输入的 Promise 失败时立即失败。
基本语法
Promise.all(iterable);
参数 iterable
是一个可迭代对象(如数组),包含多个 Promise。
成功情况
当所有 Promise 都成功时,Promise.all()
返回的 Promise 将以一个包含所有结果的数组来解决,结果的顺序与输入的 Promise 顺序一致。
const promise1 = Promise.resolve(1);
const promise2 = new Promise((resolve) => setTimeout(() => resolve(2), 100));
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then(values => {
console.log(values); // 输出: [1, 2, 3]
});
失败情况
如果任何一个 Promise 失败,Promise.all()
返回的 Promise 将立即失败,并带有第一个失败的 Promise 的错误信息。
const promise1 = Promise.resolve(1);
const promise2 = Promise.reject(new Error('出错了'));
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then(values => {
console.log('这不会执行');
})
.catch(error => {
console.error(error.message); // 输出: 出错了
});
应用场景
- 并行数据获取:同时获取多个数据源的信息
function fetchAllData() {
const userPromise = fetch('/api/user').then(res => res.json());
const postsPromise = fetch('/api/posts').then(res => res.json());
const commentsPromise = fetch('/api/comments').then(res => res.json());
return Promise.all([userPromise, postsPromise, commentsPromise])
.then(([user, posts, comments]) => {
return {
user,
posts,
comments
};
});
}
fetchAllData()
.then(data => {
console.log('所有数据:', data);
})
.catch(error => {
console.error('获取数据失败:', error);
});
- 批量操作:对多个项目执行相同的异步操作
function uploadFiles(files) {
const uploadPromises = files.map(file => {
return uploadFile(file);
});
return Promise.all(uploadPromises);
}
uploadFiles([file1, file2, file3])
.then(results => {
console.log('所有文件上传成功:', results);
})
.catch(error => {
console.error('文件上传失败:', error);
});
Promise.race()
Promise.race()
方法接收一个 Promise 数组作为输入,并返回一个新的 Promise,该 Promise 在输入的任何一个 Promise 解决或拒绝时立即解决或拒绝。
基本语法
Promise.race(iterable);
参数 iterable
是一个可迭代对象(如数组),包含多个 Promise。
使用示例
const promise1 = new Promise(resolve => setTimeout(() => resolve('第一个'), 500));
const promise2 = new Promise(resolve => setTimeout(() => resolve('第二个'), 100));
const promise3 = new Promise((resolve, reject) => setTimeout(() => reject(new Error('第三个失败')), 300));
Promise.race([promise1, promise2, promise3])
.then(value => {
console.log(value); // 输出: 第二个 (因为 promise2 最快完成)
})
.catch(error => {
console.error(error); // 不会执行,因为 promise2 在 promise3 拒绝前就已经解决了
});
应用场景
- 超时处理:为异步操作设置超时限制
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('https://api.example.com/data', 3000)
.then(response => response.json())
.then(data => console.log('数据:', data))
.catch(error => console.error('错误:', error.message));
- 获取最快的数据源:从多个数据源获取相同的数据,使用最先返回的结果
function fetchFromFastestSource() {
const source1 = fetch('https://api1.example.com/data').then(res => res.json());
const source2 = fetch('https://api2.example.com/data').then(res => res.json());
const source3 = fetch('https://api3.example.com/data').then(res => res.json());
return Promise.race([source1, source2, source3]);
}
fetchFromFastestSource()
.then(data => {
console.log('最快的数据源返回:', data);
})
.catch(error => {
console.error('所有数据源都失败:', error);
});
Promise.allSettled()
Promise.allSettled()
方法接收一个 Promise 数组作为输入,并返回一个新的 Promise,该 Promise 在所有输入的 Promise 都已完成(无论是成功还是失败)时解决。
基本语法
Promise.allSettled(iterable);
参数 iterable
是一个可迭代对象(如数组),包含多个 Promise。
返回值
Promise.allSettled()
返回的 Promise 解决后的结果是一个对象数组,每个对象表示对应的 Promise 结果:
- 对于成功的 Promise:
{ status: 'fulfilled', value: result }
- 对于失败的 Promise:
{ status: 'rejected', reason: error }
使用示例
const promise1 = Promise.resolve(1);
const promise2 = Promise.reject(new Error('出错了'));
const promise3 = Promise.resolve(3);
Promise.allSettled([promise1, promise2, promise3])
.then(results => {
console.log(results);
// 输出:
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: Error: 出错了 },
// { status: 'fulfilled', value: 3 }
// ]
});
应用场景
- 批量操作结果汇总:执行多个独立的异步操作,并汇总所有结果(包括成功和失败)
function processItems(items) {
const promises = items.map(item => processItem(item));
return Promise.allSettled(promises)
.then(results => {
const successful = results
.filter(result => result.status === 'fulfilled')
.map(result => result.value);
const failed = results
.filter(result => result.status === 'rejected')
.map(result => result.reason);
return {
successful,
failed,
totalSuccess: successful.length,
totalFailed: failed.length
};
});
}
processItems([item1, item2, item3, item4])
.then(summary => {
console.log(`处理成功: ${summary.totalSuccess}, 失败: ${summary.totalFailed}`);
console.log('成功项目:', summary.successful);
console.log('失败项目:', summary.failed);
});
- 健康检查:检查多个服务的状态,即使部分服务不可用也能获取完整报告
function checkServicesHealth(services) {
const healthChecks = services.map(service => {
return fetch(`${service.url}/health`)
.then(response => {
if (!response.ok) throw new Error(`${service.name} 不健康`);
return { name: service.name, status: 'healthy' };
})
.catch(error => {
throw { name: service.name, status: 'unhealthy', error: error.message };
});
});
return Promise.allSettled(healthChecks)
.then(results => {
return results.map(result => {
if (result.status === 'fulfilled') {
return result.value;
} else {
return result.reason;
}
});
});
}
Promise.any()
Promise.any()
方法接收一个 Promise 数组作为输入,并返回一个新的 Promise,该 Promise 在输入的任何一个 Promise 成功时立即成功,或者在所有输入的 Promise 都失败时才失败。
基本语法
Promise.any(iterable);
参数 iterable
是一个可迭代对象(如数组),包含多个 Promise。
成功情况
当任何一个 Promise 成功时,Promise.any()
返回的 Promise 将立即成功,并带有第一个成功的 Promise 的值。
const promise1 = Promise.reject(new Error('错误 1'));
const promise2 = new Promise(resolve => setTimeout(() => resolve('成功'), 100));
const promise3 = Promise.reject(new Error('错误 3'));
Promise.any([promise1, promise2, promise3])
.then(value => {
console.log(value); // 输出: 成功
})
.catch(error => {
console.error(error); // 不会执行
});
失败情况
如果所有 Promise 都失败,Promise.any()
返回的 Promise 将失败,并带有一个 AggregateError
对象,该对象包含所有失败的原因。
const promise1 = Promise.reject(new Error('错误 1'));
const promise2 = Promise.reject(new Error('错误 2'));
const promise3 = Promise.reject(new Error('错误 3'));
Promise.any([promise1, promise2, promise3])
.then(value => {
console.log(value); // 不会执行
})
.catch(error => {
console.error(error); // 输出: AggregateError: All promises were rejected
console.error(error.errors); // 输出: [Error: 错误 1, Error: 错误 2, Error: 错误 3]
});
应用场景
- 资源冗余:从多个备份源获取资源,只要有一个成功即可
function fetchFromAnySource(urls) {
const fetchPromises = urls.map(url => {
return fetch(url)
.then(response => {
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return response.json();
});
});
return Promise.any(fetchPromises);
}
fetchFromAnySource([
'https://primary-api.example.com/data',
'https://backup1-api.example.com/data',
'https://backup2-api.example.com/data'
])
.then(data => {
console.log('成功获取数据:', data);
})
.catch(error => {
console.error('所有数据源都失败:', error);
});
- 用户体验优化:尝试多种方式完成任务,采用最先成功的结果
function authenticateUser(credentials) {
const methods = [
biometricAuth(credentials),
passwordAuth(credentials),
otpAuth(credentials)
];
return Promise.any(methods)
.then(authResult => {
console.log('认证成功:', authResult);
return authResult;
})
.catch(error => {
console.error('所有认证方式都失败:', error);
throw new Error('认证失败,请重试');
});
}
其他 Promise 静态方法
Promise.resolve()
Promise.resolve()
方法返回一个以给定值解决的 Promise 对象。
// 创建一个已解决的 Promise
const resolvedPromise = Promise.resolve('已解决');
resolvedPromise.then(value => {
console.log(value); // 输出: 已解决
});
// 如果传入的是一个 Promise,则直接返回该 Promise
const originalPromise = new Promise(resolve => setTimeout(() => resolve('原始 Promise'), 1000));
const passedPromise = Promise.resolve(originalPromise);
console.log(originalPromise === passedPromise); // 输出: true
Promise.reject()
Promise.reject()
方法返回一个带有拒绝原因的 Promise 对象。
// 创建一个已拒绝的 Promise
const rejectedPromise = Promise.reject(new Error('已拒绝'));
rejectedPromise
.then(value => {
console.log('这不会执行');
})
.catch(error => {
console.error(error.message); // 输出: 已拒绝
});
自定义 Promise 组合
除了内置的 Promise 组合方法,我们还可以创建自定义的组合函数来满足特定需求。
顺序执行 Promise
function sequentialPromises(promiseFunctions) {
return promiseFunctions.reduce(
(chain, promiseFunction) => chain.then(results => {
return promiseFunction().then(result => {
return [...results, result];
});
}),
Promise.resolve([])
);
}
// 使用示例
const functions = [
() => new Promise(resolve => setTimeout(() => resolve('第一步'), 100)),
() => new Promise(resolve => setTimeout(() => resolve('第二步'), 200)),
() => new Promise(resolve => setTimeout(() => resolve('第三步'), 300))
];
sequentialPromises(functions)
.then(results => {
console.log(results); // 输出: ['第一步', '第二步', '第三步']
});
带重试的 Promise
function promiseWithRetry(promiseFunction, maxRetries = 3, delay = 1000) {
return new Promise((resolve, reject) => {
function attempt(retryCount) {
promiseFunction()
.then(resolve)
.catch(error => {
if (retryCount < maxRetries) {
console.log(`尝试失败,${delay}ms 后重试 (${retryCount + 1}/${maxRetries})...`);
setTimeout(() => attempt(retryCount + 1), delay);
} else {
reject(error);
}
});
}
attempt(0);
});
}
// 使用示例
function unstableOperation() {
return new Promise((resolve, reject) => {
const random = Math.random();
if (random < 0.7) { // 70% 的失败率
reject(new Error('操作失败'));
} else {
resolve('操作成功');
}
});
}
promiseWithRetry(unstableOperation, 5, 500)
.then(result => {
console.log('最终结果:', result);
})
.catch(error => {
console.error('所有重试都失败:', error.message);
});
带缓存的 Promise
function createCachedPromise(promiseFunction, cacheTime = 60000) {
const cache = {
value: null,
timestamp: 0
};
return function(...args) {
const now = Date.now();
// 如果缓存有效
if (cache.value && now - cache.timestamp < cacheTime) {
console.log('使用缓存的结果');
return Promise.resolve(cache.value);
}
// 缓存无效,执行实际操作
return promiseFunction(...args)
.then(result => {
// 更新缓存
cache.value = result;
cache.timestamp = now;
console.log('缓存新结果');
return result;
});
};
}
// 使用示例
function fetchData() {
console.log('执行实际的数据获取...');
return new Promise(resolve => {
setTimeout(() => resolve({ data: '一些数据', timestamp: Date.now() }), 1000);
});
}
const cachedFetch = createCachedPromise(fetchData, 5000);
// 第一次调用 - 将执行实际操作
cachedFetch().then(data => console.log('第一次调用:', data));
// 2秒后再次调用 - 将使用缓存
setTimeout(() => {
cachedFetch().then(data => console.log('2秒后调用:', data));
}, 2000);
// 6秒后再次调用 - 缓存已过期,将重新执行操作
setTimeout(() => {
cachedFetch().then(data => console.log('6秒后调用:', data));
}, 6000);
Promise 组合的最佳实践
1. 错误处理
在使用 Promise 组合方法时,务必添加适当的错误处理:
Promise.all([promise1, promise2, promise3])
.then(results => {
// 处理结果
})
.catch(error => {
// 处理错误
console.error('发生错误:', error);
// 可能的恢复策略
return fallbackData;
});
2. 避免 Promise 嵌套
使用 Promise 组合方法时,避免嵌套 Promise,应该保持扁平的链式结构:
// 不好的做法
Promise.all([promise1, promise2])
.then(results => {
Promise.all([promise3, promise4])
.then(moreResults => {
// 处理所有结果
});
});
// 好的做法
Promise.all([promise1, promise2])
.then(firstResults => {
return Promise.all([promise3, promise4])
.then(secondResults => {
return [...firstResults, ...secondResults];
});
})
.then(allResults => {
// 处理所有结果
});
3. 合理选择组合方法
根据具体需求选择合适的 Promise 组合方法:
- 需要所有 Promise 都成功:
Promise.all()
- 需要任意一个 Promise 成功或失败:
Promise.race()
- 需要等待所有 Promise 完成,无论成功失败:
Promise.allSettled()
- 需要任意一个 Promise 成功,或所有 Promise 都失败:
Promise.any()
4. 处理空数组
注意 Promise 组合方法对空数组的处理:
Promise.all([]) // 立即解决,结果为 []
.then(results => {
console.log(results); // 输出: []
});
Promise.race([]) // 永远处于 pending 状态
.then(result => {
console.log('这不会执行');
});
Promise.allSettled([]) // 立即解决,结果为 []
.then(results => {
console.log(results); // 输出: []
});
Promise.any([]) // 拒绝,错误为 AggregateError: All promises were rejected
.catch(error => {
console.error(error);
});
5. 避免过度并行
在处理大量 Promise 时,考虑限制并行执行的数量,以避免资源耗尽:
function processWithConcurrencyLimit(items, concurrency, processor) {
const results = [];
let index = 0;
let running = 0;
let completed = 0;
return new Promise((resolve, reject) => {
function startNext() {
if (index >= items.length && running === 0) {
// 所有任务已完成
resolve(results);
return;
}
while (running < concurrency && index < items.length) {
const currentIndex = index++;
running++;
processor(items[currentIndex])
.then(result => {
results[currentIndex] = result;
running--;
completed++;
startNext();
})
.catch(error => {
reject(error);
});
}
}
startNext();
});
}
// 使用示例
const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
function processItem(item) {
return new Promise(resolve => {
setTimeout(() => {
console.log(`处理项目 ${item}`);
resolve(item * 2);
}, Math.random() * 1000);
});
}
// 最多同时处理 3 个项目
processWithConcurrencyLimit(items, 3, processItem)
.then(results => {
console.log('所有结果:', results);
})
.catch(error => {
console.error('处理出错:', error);
});
总结
Promise 组合方法提供了强大的工具来管理多个异步操作,使我们能够以不同的方式组合和协调 Promise:
- Promise.all() 适用于需要所有 Promise 都成功的场景,如并行数据获取。
- Promise.race() 适用于需要最快响应的场景,如超时处理或从多个源获取数据。
- Promise.allSettled() 适用于需要等待所有操作完成并获取完整结果的场景,无论成功失败。
- Promise.any() 适用于需要任意一个成功结果的场景,如冗余请求或多种认证方式。
除了内置方法,我们还可以创建自定义的 Promise 组合函数来满足特定需求,如顺序执行、重试机制或缓存策略。
通过合理使用这些 Promise 组合方法和最佳实践,我们可以更有效地管理复杂的异步操作,提高代码的可读性和可维护性。