各位朋友,大家好! 今天咱们来聊聊JavaScript里Promise家族的几个扛把子:Promise.all()
, Promise.race()
, Promise.allSettled()
, 和 Promise.any()
。 别看名字有点绕,其实它们的功能和应用场景都挺有意思的。 咱们争取用大白话把它们讲清楚,再配上几个小例子,保证你听完就能上手。
开场白:Promise的那些事儿
在正式开始之前,咱们先简单回顾一下Promise。 Promise这玩意儿,说白了,就是用来处理异步操作的。 想象一下,你要去餐厅点菜,服务员告诉你菜可能要等一会儿,你不可能傻乎乎地一直站在那儿等吧? 你可以先干点别的,等菜做好了服务员再通知你。 Promise就扮演了这个“服务员”的角色,它代表着一个异步操作的最终结果,可能是成功,也可能是失败。
Promise有三种状态:
- pending (等待中): 初始状态,表示异步操作尚未完成。
- fulfilled (已成功): 异步操作成功完成。
- rejected (已失败): 异步操作失败。
好了,有了这个基础,咱们就可以开始深入了解那四个Promise方法了。
第一位重量级选手:Promise.all()
Promise.all()
,顾名思义,就是“全部都要”。 它接收一个Promise数组(或者任何可迭代对象,只要里面的元素都是Promise),只有当数组中所有的Promise都成功时,它才会成功,并返回一个包含所有Promise结果的数组(顺序和输入顺序一致)。 如果其中任何一个Promise失败,Promise.all()
就会立即失败,并返回第一个失败的Promise的reason。
应用场景:
- 并发请求: 假设你需要同时从多个API获取数据,只有当所有数据都获取成功后,才能进行下一步操作。 比如,你需要获取用户的信息、用户的订单列表、用户的积分信息,这三个请求可以并行发送,只有全部成功才能渲染页面。
function getUserInfo(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId > 0) {
resolve({ id: userId, name: '张三' });
} else {
reject('无效的用户ID');
}
}, 500); // 模拟网络延迟
});
}
function getUserOrders(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId > 0) {
resolve([{ orderId: 1, amount: 100 }, { orderId: 2, amount: 200 }]);
} else {
reject('无效的用户ID');
}
}, 300); // 模拟网络延迟
});
}
function getUserPoints(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId > 0) {
resolve(500);
} else {
reject('无效的用户ID');
}
}, 400); // 模拟网络延迟
});
}
Promise.all([getUserInfo(1), getUserOrders(1), getUserPoints(1)])
.then(results => {
const [userInfo, userOrders, userPoints] = results;
console.log('用户信息:', userInfo);
console.log('用户订单:', userOrders);
console.log('用户积分:', userPoints);
})
.catch(error => {
console.error('获取数据失败:', error);
});
// 如果userId为0,则会触发catch块
Promise.all([getUserInfo(0), getUserOrders(0)])
.then(results => {
// 不会执行到这里
})
.catch(error => {
console.error('获取数据失败:', error); // 输出: 获取数据失败: 无效的用户ID
});
- 表单验证: 你可以同时验证多个表单字段,只有所有字段都验证通过,才能提交表单。
第二位急性子选手:Promise.race()
Promise.race()
,顾名思义,就是“赛跑”。 它也接收一个Promise数组,但它只关心第一个完成的Promise,不管是成功还是失败。 哪个Promise最先完成,Promise.race()
就返回那个Promise的结果(如果成功就返回resolve的值,如果失败就返回reject的原因)。
应用场景:
- 超时控制: 你可以给一个请求设置超时时间,如果请求在规定时间内没有返回,就认为请求失败。
function fetchData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`从 ${url} 获取的数据`);
}, 500); // 模拟网络延迟
});
}
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('请求超时');
}, ms);
});
}
Promise.race([fetchData('https://example.com/api'), timeout(300)])
.then(result => {
console.log('请求成功:', result);
})
.catch(error => {
console.error('请求失败:', error); // 输出: 请求失败: 请求超时
});
// 如果将timeout时间设置为1000ms,则会输出:请求成功: 从 https://example.com/api 获取的数据
- 备用方案: 你可以设置多个备用API,哪个API最先返回数据,就使用哪个API的结果。
第三位稳重选手:Promise.allSettled()
Promise.allSettled()
,可以说是Promise.all()
的升级版。 它也接收一个Promise数组,但它会等待所有Promise都完成,不管是成功还是失败。 然后,它返回一个包含所有Promise结果的数组,每个结果都是一个对象,包含status
和value
或reason
属性。
status
:"fulfilled"
或"rejected"
,表示Promise的状态。value
: 如果Promise成功,则包含Promise的resolve值。reason
: 如果Promise失败,则包含Promise的reject原因。
应用场景:
- 日志记录: 你可以同时向多个日志服务器发送日志,即使某些服务器失败,也不影响其他服务器的日志记录。
- 数据备份: 你可以同时将数据备份到多个存储服务器,即使某些服务器失败,也不影响数据的完整性。
- 不关心单个请求结果的场景: 某些情况下,你只想知道所有请求是否都结束了,而不在乎每个请求的具体结果。
function fetchData(url, shouldFail) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldFail) {
reject(`从 ${url} 获取数据失败`);
} else {
resolve(`从 ${url} 获取的数据`);
}
}, 500); // 模拟网络延迟
});
}
Promise.allSettled([
fetchData('https://example.com/api1', false),
fetchData('https://example.com/api2', true),
fetchData('https://example.com/api3', false),
])
.then(results => {
console.log(results);
// 输出:
// [
// { status: 'fulfilled', value: '从 https://example.com/api1 获取的数据' },
// { status: 'rejected', reason: '从 https://example.com/api2 获取数据失败' },
// { status: 'fulfilled', value: '从 https://example.com/api3 获取的数据' }
// ]
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('请求成功:', result.value);
} else {
console.error('请求失败:', result.reason);
}
});
});
第四位乐观选手:Promise.any()
Promise.any()
,是ES2021新增的方法,它也接收一个Promise数组。 与Promise.all()
相反,Promise.any()
只要有一个Promise成功,它就会成功,并返回第一个成功的Promise的结果。 如果所有Promise都失败,Promise.any()
就会失败,并返回一个AggregateError
对象,该对象包含所有失败的Promise的reason。
应用场景:
- 选择最佳服务器: 你可以向多个服务器发送请求,哪个服务器最先返回成功的结果,就使用哪个服务器。 比如,你有多个CDN节点,你想选择速度最快的那个。
- 容错处理: 你可以尝试使用多个API,只要有一个API成功,就认为操作成功。
function fetchData(url, shouldFail) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldFail) {
reject(`从 ${url} 获取数据失败`);
} else {
resolve(`从 ${url} 获取的数据`);
}
}, 500); // 模拟网络延迟
});
}
Promise.any([
fetchData('https://example.com/api1', true),
fetchData('https://example.com/api2', false),
fetchData('https://example.com/api3', true),
])
.then(result => {
console.log('请求成功:', result); // 输出: 请求成功: 从 https://example.com/api2 获取的数据
})
.catch(error => {
console.error('请求失败:', error);
});
// 如果所有请求都失败
Promise.any([
fetchData('https://example.com/api1', true),
fetchData('https://example.com/api2', true),
])
.then(result => {
// 不会执行到这里
})
.catch(error => {
console.error('请求全部失败:', error); // 输出 AggregateError: All promises were rejected
console.log(error.errors); // 输出包含所有reject reason的数组
});
总结:四兄弟的差异
为了方便大家记忆和区分,我们用一个表格来总结一下这四个Promise方法的差异:
方法 | 成功条件 | 失败条件 | 返回值 | 应用场景 |
---|---|---|---|---|
Promise.all() |
所有Promise都成功 | 任何一个Promise失败 | 包含所有成功Promise结果的数组 (顺序与输入一致) | 并发请求,表单验证等,需要所有操作都成功才能进行下一步的场景 |
Promise.race() |
任何一个Promise完成 (成功或失败) | 无 (只关心第一个完成的Promise) | 第一个完成的Promise的结果 (成功则resolve的值,失败则reject的原因) | 超时控制,备用方案等,只需要最快的结果的场景 |
Promise.allSettled() |
所有Promise都完成 (成功或失败) | 无 (总是等待所有Promise完成) | 包含所有Promise结果的数组,每个结果包含状态(fulfilled/rejected)和值/原因 | 日志记录,数据备份等,需要知道所有操作是否都结束的场景,不关心单个操作的结果 |
Promise.any() |
任何一个Promise成功 | 所有Promise都失败 | 第一个成功的Promise的结果 | 选择最佳服务器,容错处理等,只需要一个操作成功即可的场景 |
一个综合案例:图片加载
假设你需要在网页上加载多张图片,你想同时加载所有图片,但如果其中某张图片加载失败,你不想中断其他图片的加载。 同时,你还想给每张图片设置一个加载超时时间。
function loadImage(url, timeout) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
resolve(img);
};
img.onerror = () => {
reject(`加载图片失败: ${url}`);
};
img.src = url;
// 超时处理
setTimeout(() => {
reject(`加载图片超时: ${url}`);
}, timeout);
});
}
const imageUrls = [
'https://example.com/image1.jpg',
'https://example.com/image2.png',
'https://example.com/image3.gif',
'https://example.com/image4.jpeg',
];
const imagePromises = imageUrls.map(url => loadImage(url, 2000)); // 2秒超时
Promise.allSettled(imagePromises)
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
const img = result.value;
document.body.appendChild(img); // 将加载成功的图片添加到页面
} else {
console.error(`图片 ${imageUrls[index]} 加载失败: ${result.reason}`);
}
});
});
在这个例子中,我们使用了Promise.allSettled()
来确保所有图片都会尝试加载,即使某些图片加载失败也不会影响其他图片。 同时,我们在loadImage
函数中使用了setTimeout
来设置超时时间,如果图片在规定时间内没有加载完成,就会被认为加载失败。
总结与展望
好了,关于Promise.all()
, Promise.race()
, Promise.allSettled()
, 和 Promise.any()
,咱们就先聊到这里。 希望通过今天的讲解,大家能够对它们的作用和区别有更清晰的认识,并在实际开发中灵活运用。
记住,理解这些Promise方法,不仅能让你的代码更简洁、更优雅,还能让你更好地处理异步操作,提升应用的性能和用户体验。
当然,Promise的世界还有很多值得探索的地方,比如async/await
,Promise.withResolvers()
等等。 以后有机会,咱们再一起深入研究。 感谢各位的收听!