在现代 JavaScript 编程中,异步操作已经成为开发中不可或缺的一部分,而 Promise
则是处理异步操作的核心工具。随着异步编程需求的增加,JavaScript 提供了多种处理方式,其中 Promise
和 Promise.all
是两个非常重要且常用的异步操作方法。在本文中,我们将深入探讨 Promise
及其进阶用法 Promise.all
,并讨论它们的使用场景和最佳实践。
1. Promise 的基本概念
Promise
是一种用于处理异步操作的对象。它代表着一个在未来可能完成或失败的操作,并且能够以一种线性、直观的方式来处理异步逻辑。Promise
对象有三种状态:
- Pending(待定):初始状态,操作尚未完成。
- Fulfilled(已完成):操作成功完成。
- Rejected(已拒绝):操作失败。
一个 Promise
的典型使用方式如下:
new Promise((resolve, reject) => {
// 执行一些异步操作
setTimeout(() => {
let success = true;
if (success) {
resolve("操作成功!");
} else {
reject("操作失败!");
}
}, 1000);
}).then(result => {
console.log(result); // "操作成功!"
}).catch(error => {
console.log(error); // "操作失败!"
});
在这个例子中,Promise
被创建后,它会立即执行传入的函数,并在操作完成时调用 resolve
或 reject
,然后通过 then
方法处理成功的结果,通过 catch
方法处理失败的情况。
2. Promise.all 的使用场景
Promise.all
是 Promise
的一个高级用法,适用于当我们有多个异步操作需要并行执行,并且希望在所有操作都成功后继续执行后续逻辑时。它接收一个包含多个 Promise
对象的数组,并返回一个新的 Promise
对象。
Promise.all
的工作机制如下:
- 当数组中的所有
Promise
对象都Fulfilled
时,Promise.all
返回的Promise
状态变为Fulfilled
,并返回一个包含所有Promise
返回值的数组。 - 如果数组中的任意一个
Promise
进入Rejected
状态,Promise.all
返回的Promise
状态立即变为Rejected
,并返回第一个Rejected
的错误。
下面是一个 Promise.all
的简单示例:
let promise1 = Promise.resolve(3);
let promise2 = 42;
let promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3])
.then(values => {
console.log(values); // [3, 42, "foo"]
})
.catch(error => {
console.log(error);
});
在这个例子中,Promise.all
接收了三个 Promise
,它们都成功了,因此 then
回调函数会接收到一个数组,包含三个 Promise
返回的值。
3. 使用 Promise.all 处理并行异步操作
在实际开发中,我们常常需要同时处理多个异步操作,例如从多个 API 获取数据、并行处理多个文件或数据库查询等。这种情况下,Promise.all
可以有效地提高代码的执行效率,因为它可以同时发起所有请求,而不是一个接一个地等待前一个请求完成。
举个例子,假设我们需要从三个不同的 API 获取数据,然后将这些数据进行处理:
function fetchData(url) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`Data from ${url}`);
}, Math.random() * 1000);
});
}
Promise.all([
fetchData('https://api1.com'),
fetchData('https://api2.com'),
fetchData('https://api3.com')
]).then(results => {
console.log(results);
}).catch(error => {
console.log(error);
});
在这个例子中,三个 fetchData
请求会同时开始,并且 Promise.all
会等待所有请求都完成后再继续处理结果。
4. Promise.all 的注意事项
虽然 Promise.all
提供了一种强大的并行处理方式,但也有一些需要注意的地方:
-
单点失败问题:如果任意一个
Promise
被拒绝(Rejected
),Promise.all
返回的Promise
也会立即Rejected
,这意味着即使其他Promise
已经成功完成,整个Promise.all
也会失败。这种行为在某些情况下可能并不理想。 -
顺序不可控:
Promise.all
返回结果的顺序与传入Promise
的顺序一致,而不是实际完成的顺序。如果你需要根据完成时间顺序处理Promise
,可能需要其他方法,例如Promise.race
。 -
并发限制:在某些情况下,过多的并发请求可能导致性能问题或服务器负载过高。可以考虑使用
Promise.all
的变种,如p-limit
库来限制并发数。
5. 手写Promise
class NewPromise {
constructor(func) {
//判断当前执行到第几个then和catch的回调函数
this.currentIndex = 0;
//resolve函数,执行当前索引后面最近的then的回调函数
this.res = (arg) => {
//找到最近的一个then的回调函数
for (let i = this.currentIndex; i < this.funcArr.length; i++) {
if (this.funcArr[i].type === "then") {
this.currentIndex++;
break;
}
}
//记录返回值
const resReturn = this.funcArr[this.currentIndex - 1].func(arg);
//判断返回值是否存在,如不存在则执行finally,如存在则判断类型是否为NewPromise也就是此类
if (resReturn) {
if (resReturn instanceof NewPromise) {
resReturn.currentIndex = this.currentIndex;
resReturn.funcArr = this.funcArr;
resReturn.finallyFunc = this.finallyFunc;
} else {
this.res(resReturn);
}
} else {
if (this.finallyFunc) {
this.finallyFunc();
}
}
};
//reject函数,执行当前索引后面最近的catch的回调函数
this.rej = (arg) => {
//找到最近的一个catch的回调函数
for (let i = this.currentIndex; i < this.funcArr.length; i++) {
if (this.funcArr[i].type === "catch") {
this.currentIndex++;
break;
}
}
const rejReturn = this.funcArr[this.currentIndex - 1].func(arg);
//判断返回值是否存在,如不存在则执行finally,如存在则判断类型是否为NewPromise也就是此类
if (rejReturn) {
if (resReturn instanceof NewPromise) {
resReturn.currentIndex = this.currentIndex;
resReturn.funcArr = this.funcArr;
resReturn.finallyFunc = this.finallyFunc;
} else {
this.rej(resReturn);
}
} else {
if (this.finallyFunc) {
this.finallyFunc();
}
}
};
//预备数组,记录全部回调函数,并以对象形式储存,其中包括type判断是then还是catch,以及对应的函数指针
this.funcArr = [];
//保存finally回调函数
this.finallyFunc;
//执行newpromise的参数函数,并将写好的reslove和reject函数传入
func(this.res, this.rej);
}
//then,将传入的函数放入预备数组
then(func) {
this.funcArr.push({
type: "then",
func,
});
return this;
}
//catch,将传入的函数防御预备数组
catch(func) {
this.funcArr.push({
type: "catch",
func,
});
return this;
}
//finally,最终必须执行的函数
finally(func) {
this.finallyFunc = func;
}
}
new NewPromise((res, rej) => {
setTimeout(() => {
res(6);
}, 1000);
})
.then((res) => {
console.log(res);
})
.catch((rej) => {
console.log(rej);
});