JavaScript_Promise
1. MDN
MDN:Promise
对象表示异步操作最终的完成(或失败)以及其结果值。
描述
一个 Promise
是一个代理,它代表一个在创建 promise 时不一定已知的值。它允许你将处理程序与异步操作的最终成功值或失败原因关联起来。这使得异步方法可以像同步方法一样返回值:异步方法不会立即返回最终值,而是返回一个 promise,以便在将来的某个时间点提供该值。
一个 Promise
必然处于以下几种状态之一:
- 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled):意味着操作成功完成。
- 已拒绝(rejected):意味着操作失败。
一个待定的 Promise 最终状态可以是已兑现并返回一个值,或者是已拒绝并返回一个原因(错误)。当其中任意一种情况发生时,通过 Promise 的 then
方法串联的处理程序将被调用。如果绑定相应处理程序时 Promise 已经兑现或拒绝,这处理程序将被立即调用,因此在异步操作完成和绑定处理程序之间不存在竞态条件。
如果一个 Promise 已经被兑现或拒绝,即不再处于待定状态,那么则称之为已敲定(settled)。
你还会听到使用已解决(resolved)这个术语来描述 Promise——这意味着该 Promise 已经敲定(settled),或为了匹配另一个 Promise 的最终状态而被“锁定(lock-in)”,进一步解决或拒绝它都没有影响。原始 Promise 提案中的 States and fates 文档包含了更多关于 Promise 术语的细节。在口语中,“已解决”的 Promise 通常等价于“已兑现”的 Promise,但是正如“States and fates”所示,已解决的 Promise 也可以是待定或拒绝的。例如:
new Promise((resolveOuter) => {
resolveOuter(
new Promise((resolveInner) => {
setTimeout(resolveInner, 1000);
}),
);
});
此 Promise 在创建时已经被解决(因为 resolveOuter
是同步调用的),但它是用另一个 Promise 解决的,因此在内部 Promise 兑现的 1 秒之后才会被兑现。在实践中,“解决”过程通常是在幕后完成的,不可观察,只有其兑现或拒绝是可观察的。
2. 对 Promise 的理解
Promise 是 JavaScript 中用于处理异步操作的一种对象。它代表了一个尚未完成但承诺会在未来某个时候完成的操作,并允许你在操作完成前注册回调函数。Promise 对象主要有三种状态:
- 1)Pending(等待):初始状态,操作未完成,也没有成功或失败。
- 2)Fulfilled(已实现):操作成功完成,并得到了一个值。
- 3)Rejected(已拒绝):操作失败,并返回一个原因。
3. Promise 解决了什么问题
Promise 解决了回调地狱和异步编程管理的问题。在传统的 JavaScript 异步编程中,我们通常会使用回调函数来处理异步操作。随着异步操作逐渐增多,回调函数会嵌套的越来越深,这种现象被称为回调地狱。代码结构变得复杂且难以维护。Promise 为异步编程提供了一种更加优雅的解决方案,使用链式调用来处理异步操作,从而提高了代码的可读性和可维护性。
3.1 回调地狱(Callback Hell)
回调地狱是指回调函数相互嵌套,导致代码难以阅读和维护。举个例子:
fs.readFile('file1.txt', function(err, data1) {
if (err) throw err;
fs.readFile('file2.txt', function(err, data2) {
if (err) throw err;
fs.readFile('file3.txt', function(err, data3) {
if (err) throw err;
// 继续嵌套下去...
});
});
});
如上所示,每个嵌套的回调函数都需要等待前一个操结束后再开始,下一个回调函数嵌套得越来越深,这使得代码变得不直观。
3.2 Promise 的基本用法
Promise 是一个构造函数,通过 new Promise
可以创建一个 Promise 实例。示例如下:
let myPromise = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve("成功!");
}, 1000);
});
myPromise.then(function(value) {
console.log(value); // 成功!
}).catch(function(error) {
console.log(error);
});
let promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
let success = true; // 这里可以是任何异步操作结果
if (success) {
resolve('操作成功'); // 操作成功,传递结果
} else {
reject('操作失败'); // 操作失败,传递原因
}
}, 1000);
});
promise.then(result => {
console.log(result); // 操作成功时处理
}).catch(error => {
console.error(error); // 操作失败时处理
});
3.3 链式调用
Promise 的链式调用可以让我们避免回调地狱。每个 .then
方法都会返回一个新的 Promise,因此可以使用链式调用处理多个异步操作。示例如下:
fetch(url1)
.then(response => response.json())
.then(data => fetch(url2))
.then(response => response.json())
.then(data => fetch(url3))
.then(response => response.json())
.then(data => {
console.log("All requests completed!");
})
.catch(error => {
console.error("Error:", error);
});
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000); // 初始 Promise
}).then(result => {
console.log(result); // 输出 1
return result * 2; // 返回一个新的值
}).then(result => {
console.log(result); // 输出 2
return result * 2;
}).then(result => {
console.log(result); // 输出 4
}).catch(error => {
console.error(error);
});
相比回调地狱,Promise 使得代码更加扁平和可读。
4. 静态方法
4.1 Promise.all
Promise 提供了一些辅助方法,比如 Promise.all
和 Promise.race
,可以并行处理多个异步操作。
Promise.all:当传入的所有 Promise 都完成(或其中一个失败)时,它会返回一个新的 Promise,新的 Promise 的值是所有 Promise 返回值组成的数组。
Promise.all([promise1, promise2, promise3]).then(values => {
console.log(values); // [结果1, 结果2, 结果3]
}).catch(error => {
console.error("Error:", error);
});
4.2 Promise.race
Promise.race:只要其中一个 Promise 解决或拒绝,它就会返回。
Promise.race([promise1, promise2, promise3]).then(value => {
console.log(value); // 最先解决或拒绝的结果
}).catch(error => {
console.error("Error:", error);
});
4.3 Promise.allsettled
Promise.allSettled:此方法也接收一个 promise 对象数组,并返回一个新的 promise。无论数组中的 promise 是解决还是拒绝,返回的 promise 都会解决,并返回一个包含每个 promise 结果对象的数组,每个结果对象包含两种属性:status("fulfilled" 或 "rejected")和 value 或 reason。
总结来说,Promise.all 更适用于处理所有 promise 都需要成功的情况,而 Promise.allSettled 更适用于需要知道每个 promise 结果的情况。
// Promise.allSettled 示例
const promise4 = Promise.resolve(3);
const promise5 = new Promise((resolve, reject) => {
setTimeout(reject, 100, 'error');
});
const promise6 = Promise.resolve(42);
Promise.allSettled([promise4, promise5, promise6]).then(results => {
results.forEach(result => console.log(result.status));
// fulfilled
// rejected
// fulfilled
});