各位观众老爷,晚上好!我是你们的老朋友,Bug终结者,今天咱们来聊聊JavaScript里一个非常实用,但有时候也容易让人掉坑里的API:Promise.all()
。
别看名字挺唬人,其实它干的事情很简单,就是并发执行一堆Promise,然后等你指定的这些Promise 全部 都完成了,它才会给你一个最终的结果。就好像你同时烤好几个披萨,只有所有披萨都烤好了,你才能开开心心地享用它们。
Promise.all()的基本用法
Promise.all()
接受一个可迭代对象(通常是数组),里面包含了一堆Promise。它会返回一个新的Promise,这个新的Promise的状态取决于参数中所有Promise的状态:
- 成功: 如果所有Promise都成功了,那么返回的Promise也会成功,并且它的resolve值是一个数组,包含所有Promise的resolve值,顺序和传入的Promise顺序一致。
- 失败: 只要有一个Promise失败了,那么返回的Promise就会立即失败,并且它的reject值是第一个失败的Promise的reject值。
来看个简单的例子:
const promise1 = Promise.resolve(3);
const promise2 = 42; // 注意:这里不是Promise,会被自动包装成Promise.resolve(42)
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo'); // 100ms后resolve
});
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values); // 输出: [3, 42, 'foo'],大约100ms后输出
})
.catch((error) => {
console.error(error); // 不会执行到这里,因为所有promise都成功了
});
在这个例子中,promise1
和promise2
已经处于resolved状态,promise3
会在100ms后resolve。所以,Promise.all()
会在大约100ms后resolve,并将所有Promise的resolve值组成一个数组返回。
Promise.all()的错误处理
重点来了!如果其中一个Promise失败了,Promise.all()
会立即reject,并且只返回第一个失败的Promise的reject值。这意味着,其他已经resolve的Promise的结果会被忽略,没有被处理。
const promise1 = Promise.resolve(3);
const promise2 = Promise.reject('出错了!'); // 立即reject
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo'); // 100ms后resolve,但没用了
});
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values); // 不会执行到这里
})
.catch((error) => {
console.error(error); // 输出: 出错了!,立即输出
});
在这个例子中,promise2
立即reject,所以Promise.all()
也会立即reject,并返回promise2
的reject值。即使promise1
已经resolve,promise3
最终也会resolve,但这些结果都会被忽略。
应用场景:并发请求数据
Promise.all()
最常见的应用场景就是并发请求多个数据接口,然后等待所有数据都返回后再进行处理。例如,你需要从三个不同的API获取用户信息、用户订单和用户地址,你可以这样做:
function getUserInfo(userId) {
return fetch(`/api/user/${userId}`).then(response => response.json());
}
function getUserOrders(userId) {
return fetch(`/api/orders/${userId}`).then(response => response.json());
}
function getUserAddress(userId) {
return fetch(`/api/address/${userId}`).then(response => response.json());
}
async function displayUserData(userId) {
try {
const [userInfo, userOrders, userAddress] = await Promise.all([
getUserInfo(userId),
getUserOrders(userId),
getUserAddress(userId)
]);
console.log('用户信息:', userInfo);
console.log('用户订单:', userOrders);
console.log('用户地址:', userAddress);
// 在这里进行数据处理和展示
} catch (error) {
console.error('获取数据失败:', error);
// 处理错误,例如显示错误信息
}
}
displayUserData(123);
这个例子中,Promise.all()
并发执行三个fetch
请求,只有当所有请求都成功返回数据后,才会将数据传递给then
回调函数。如果其中任何一个请求失败,catch
回调函数会被调用,你可以进行错误处理。
Promise.all()的局限性
虽然Promise.all()
很强大,但它也有一些局限性:
- 短板效应: 只要有一个Promise失败,整个
Promise.all()
就会失败,这可能会导致一些已经完成的Promise的结果被浪费。 - 没有取消功能: 无法取消正在执行的Promise。如果某个Promise永远无法resolve或reject,
Promise.all()
就会一直等待下去。 - 错误处理: 只能捕获第一个失败的Promise的错误,无法获取所有失败的Promise的错误信息。
替代方案:Promise.allSettled()
为了解决Promise.all()
的短板效应,ES2020引入了Promise.allSettled()
。它会等待所有Promise都settled(即resolve或reject),然后返回一个数组,包含每个Promise的结果。
const promise1 = Promise.resolve(3);
const promise2 = Promise.reject('出错了!');
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.allSettled([promise1, promise2, promise3])
.then((results) => {
console.log(results);
/*
输出:
[
{ status: 'fulfilled', value: 3 },
{ status: 'rejected', reason: '出错了!' },
{ status: 'fulfilled', value: 'foo' }
]
*/
});
Promise.allSettled()
返回的数组中,每个元素都是一个对象,包含以下属性:
status
:'fulfilled'
或'rejected'
,表示Promise的状态。value
: 如果Promise是fulfilled状态,则包含resolve值。reason
: 如果Promise是rejected状态,则包含reject值。
使用Promise.allSettled()
可以避免短板效应,你可以处理所有Promise的结果,即使其中一些Promise失败了。
Promise.race()
还有一个和Promise.all()
类似,但行为相反的API:Promise.race()
。 它接受一个可迭代对象,里面包含了一堆Promise。它会返回一个新的Promise,这个新的Promise的状态取决于参数中第一个改变状态的Promise:
- 成功: 如果第一个resolve的Promise成功了,那么返回的Promise也会成功,并且它的resolve值是第一个resolve的Promise的resolve值。
- 失败: 如果第一个reject的Promise失败了,那么返回的Promise也会立即失败,并且它的reject值是第一个reject的Promise的reject值。
就好像赛跑一样,谁先到达终点,就以谁的结果为准。
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(reject, 100, 'two');
});
Promise.race([promise1, promise2])
.then((value) => {
console.log(value); // 不会执行到这里
})
.catch((error) => {
console.log(error); // 输出: two,大约100ms后输出
});
在这个例子中,promise2
会在100ms后reject,而promise1
会在500ms后resolve。因为promise2
先改变了状态,所以Promise.race()
会立即reject,并返回promise2
的reject值。
应用场景:超时控制
Promise.race()
的一个常见应用场景是超时控制。你可以创建一个Promise,在指定的时间后reject,然后将它和你的实际Promise一起传递给Promise.race()
。如果实际的Promise在超时时间内没有resolve或reject,那么超时Promise会reject,从而导致Promise.race()
reject。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('超时了!');
}, ms);
});
}
function fetchData() {
return new Promise((resolve, reject) => {
// 模拟一个耗时操作
setTimeout(() => {
resolve('数据获取成功!');
}, 2000);
});
}
async function getDataWithTimeout() {
try {
const data = await Promise.race([fetchData(), timeout(1000)]);
console.log(data); // 如果 fetchData 在 1000ms 内 resolve,则输出数据
} catch (error) {
console.error(error); // 如果 fetchData 超过 1000ms,则输出 "超时了!"
}
}
getDataWithTimeout();
在这个例子中,timeout(1000)
会创建一个Promise,在1000ms后reject。Promise.race()
会将这个Promise和fetchData()
返回的Promise一起执行。如果fetchData()
在1000ms内resolve,那么Promise.race()
会resolve,并返回fetchData()
的结果。如果fetchData()
超过1000ms还没有resolve,那么timeout(1000)
会reject,导致Promise.race()
reject。
总结
API | 作用 | 成功条件 | 失败条件 | 适用场景 |
---|---|---|---|---|
Promise.all() |
并发执行多个Promise,等待所有Promise都完成。 | 所有Promise都resolve。 | 任意一个Promise reject。 | 并行执行独立的异步操作,需要所有操作都成功才能进行下一步。 |
Promise.allSettled() |
并发执行多个Promise,等待所有Promise都settled(resolve或reject)。 | 所有Promise都settled。 | 无,总是会resolve。 | 并行执行独立的异步操作,无论操作成功与否都需要进行下一步(例如,记录日志)。 |
Promise.race() |
并发执行多个Promise,等待第一个Promise改变状态。 | 第一个Promise resolve。 | 第一个Promise reject。 | 竞速场景,例如超时控制,或者选择最快的响应。 |
希望通过今天的讲解,大家能够更深入地了解Promise.all()
、Promise.allSettled()
和Promise.race()
的用法和注意事项。 记住,合理使用这些API可以提高代码的效率和可维护性,但也要注意它们各自的局限性,选择最适合你的场景的API。
今天的分享就到这里,感谢各位的收看!我们下次再见!