JavaScript Promise.any() 方法详解:获取第一个成功的Promise
处理多个异步任务时,有时我们只关心第一个成功的结果,比如从多个CDN加载资源、调用多个备用API接口。这种场景下,Promise.any() 就是最合适的选择。
Promise.any() 是什么?
Promise.any() 接收一个Promise数组,返回第一个成功(fulfilled)的Promise结果。如果所有Promise都失败,则返回一个AggregateError(聚合错误)。
Promise.any([promise1, promise2, promise3])
.then(firstResult => {
console.log('第一个成功的结果:', firstResult);
})
.catch(error => {
console.log('所有Promise都失败了:', error);
});
基本语法
Promise.any(iterable);
-
iterable:一个可迭代对象,是Promise数组
-
返回值:一个新的Promise
-
如果有任何一个Promise成功,则返回这个成功的结果
-
如果所有Promise都失败,则返回一个拒绝的Promise,带有AggregateError
-
三种典型场景
场景1:所有Promise都成功
代码号学习: 多个接口同时请求
const api1 = new Promise((resolve) => {
setTimeout(() => {
console.log('API1 响应慢,2秒后返回');
resolve('数据源A: 用户信息');
}, 2000);
});
const api2 = new Promise((resolve) => {
setTimeout(() => {
console.log('API2 响应快,1秒后返回');
resolve('数据源B: 用户信息');
}, 1000);
});
const api3 = new Promise((resolve) => {
setTimeout(() => {
console.log('API3 响应中等,1.5秒后返回');
resolve('数据源C: 用户信息');
}, 1500);
});
Promise.any([api1, api2, api3])
.then(result => {
console.log('最终使用的数据:', result);
// 输出: 最终使用的数据: 数据源B: 用户信息
// 因为API2最快成功
});
场景2:部分成功、部分失败
const task1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('任务1 失败');
reject('任务1出错');
}, 500);
});
const task2 = new Promise((resolve) => {
setTimeout(() => {
console.log('任务2 成功');
resolve('任务2的结果');
}, 1000);
});
const task3 = new Promise((resolve) => {
setTimeout(() => {
console.log('任务3 成功');
resolve('任务3的结果');
}, 1500);
});
Promise.any([task1, task2, task3])
.then(result => {
console.log('第一个成功的是:', result);
// 输出: 第一个成功的是: 任务2的结果
// task1虽然先失败,但被忽略了,等到task2成功就返回
});
场景3:所有Promise都失败
const p1 = new Promise((_, reject) => {
setTimeout(() => reject('错误1'), 1000);
});
const p2 = new Promise((_, reject) => {
setTimeout(() => reject('错误2'), 800);
});
const p3 = new Promise((_, reject) => {
setTimeout(() => reject('错误3'), 1200);
});
Promise.any([p1, p2, p3])
.then(result => {
console.log('成功:', result); // 不会执行到这里
})
.catch(error => {
console.log('错误类型:', error.name); // AggregateError
console.log('所有错误:', error.errors); // ['错误2', '错误1', '错误3']
// 注意:errors数组顺序按完成时间排列,不是输入顺序
});
实际应用场景
1. 多CDN资源加载
// 从多个CDN加载同一资源,谁快用谁
function loadScriptFromCDN(url) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.onload = () => resolve(url);
script.onerror = () => reject(`加载失败: ${url}`);
document.head.appendChild(script);
});
}
const cdn1 = loadScriptFromCDN('https://cdn1.example.com/library.js');
const cdn2 = loadScriptFromCDN('https://cdn2.example.com/library.js');
const cdn3 = loadScriptFromCDN('https://cdn3.example.com/library.js');
Promise.any([cdn1, cdn2, cdn3])
.then(loadedUrl => {
console.log(`脚本从 ${loadedUrl} 加载成功`);
// 初始化应用
initializeApp();
})
.catch(aggregateError => {
console.error('所有CDN都挂了,请检查网络');
showOfflineMessage();
});
2. 备用API接口
// 主API挂了自动切换到备用API
function fetchUserData(userId) {
const primaryAPI = fetch(`https://api.example.com/users/${userId}`)
.then(res => res.json());
const backupAPI = fetch(`https://backup-api.example.com/users/${userId}`)
.then(res => res.json());
const emergencyAPI = fetch(`https://emergency.example.com/users/${userId}`)
.then(res => res.json());
return Promise.any([primaryAPI, backupAPI, emergencyAPI])
.then(userData => {
console.log('获取用户数据成功');
return userData;
})
.catch(error => {
console.log('所有API都不可用');
return { id: userId, name: '游客', offline: true };
});
}
// 使用
fetchUserData(123)
.then(user => renderUserInfo(user));
3. 最快响应的搜索服务
function searchFromSource(source, keyword) {
return fetch(`/api/search/${source}q=${keyword}`)
.then(res => res.json())
.then(data => ({
source,
results: data
}));
}
function searchAll(keyword) {
const google = searchFromSource('google', keyword);
const baidu = searchFromSource('baidu', keyword);
const bing = searchFromSource('bing', keyword);
return Promise.any([google, baidu, bing])
.then(fastestResult => {
console.log(`最快响应来自: ${fastestResult.source}`);
return fastestResult.results;
})
.catch(() => {
console.log('所有搜索引擎都挂了');
return [];
});
}
// 使用
searchAll('JavaScript教程')
.then(results => displayResults(results));
Promise.any() 与他并发方法的区别
| 方法 | 特点 | 适用场景 |
|---|---|---|
| Promise.any() | 返回第一个成功的结果 | 只需要一个成功的,不关心他 |
| Promise.race() | 返回第一个完成的结果(不论成功失败) | 超时控制、竞速 |
| Promise.all() | 等待所有成功,一个失败就整体失败 | 多个并行请求,缺一不可 |
| Promise.allSettled() | 等待所有完成,返回每个的结果 | 需要知道所有任务的结果 |
// 对比示例
const fastSuccess = new Promise(resolve => setTimeout(() => resolve('成功A'), 100));
const fastFail = new Promise((_, reject) => setTimeout(() => reject('失败B'), 50));
const slowSuccess = new Promise(resolve => setTimeout(() => resolve('成功C'), 200));
// Promise.any - 只关心第一个成功
Promise.any([fastFail, fastSuccess, slowSuccess])
.then(result => console.log('any:', result)); // any: 成功A
// Promise.race - 第一个完成的(不论成功失败)
Promise.race([fastFail, fastSuccess, slowSuccess])
.then(result => console.log('race成功:', result))
.catch(error => console.log('race失败:', error)); // race失败: 失败B
// Promise.all - 所有成功才成功
Promise.all([fastSuccess, slowSuccess])
.then(results => console.log('all:', results)); // all: ['成功A', '成功C']
// Promise.allSettled - 等待所有完成
Promise.allSettled([fastFail, fastSuccess])
.then(results => console.log('allSettled:', results));
// allSettled: [
// { status: 'rejected', reason: '失败B' },
// { status: 'fulfilled', value: '成功A' }
// ]
个人经验:项目中经常用Promise.any()处理那些"多个备选,谁快用谁"的场景。比如用户上传图片时,同时传给多个图床,第一个上传成功的就拿来用。既提高了成功率,又保证了速度。
处理AggregateError
当所有Promise都失败时,catch到的错误是AggregateError,它有特殊的errors属性:
function testAllFail() {
const promises = [
Promise.reject('错误1'),
Promise.reject('错误2'),
Promise.reject('错误3')
];
return Promise.any(promises);
}
testAllFail()
.catch(err => {
if (err.name === 'AggregateError') {
console.log('总共失败数量:', err.errors.length);
err.errors.forEach((reason, index) => {
console.log(`失败${index + 1}:`, reason);
});
// 可以在这里进行降级处理
return getDefaultData();
}
});
空数组的处理
传入空数组时,Promise.any() 会立即返回一个拒绝的Promise:
Promise.any([])
.then(result => {
console.log('成功:', result); // 不会执行
})
.catch(error => {
console.log('空数组错误:', error.name); // AggregateError
console.log('错误信息:', error.message); // All promises were rejected
console.log('errors数组:', error.errors); // [] 空数组
});
课程知识要点
-
核心功能:Promise.any() 返回第一个成功的Promise结果
-
忽略失败:只要有一个成功,就忽略他所有失败
-
全失败处理:所有Promise都失败时,返回AggregateError
-
AggregateError:特殊的错误类型,errors属性包含所有失败原因
-
适用场景:多CDN加载、备用API、最快响应服务
-
与race的区别:race返回第一个完成的(失败),any只返回第一个成功的
-
空数组行为:立即返回拒绝的Promise,带有AggregateError
个人建议:用Promise.any()时一定要考虑全失败的情况,catch里做降级处理。如果不需要关心失败,只想拿第一个成功的结果,any比race更合适,因为race拿到失败的。