一、什么是 Promise?
在 JavaScript 中,Promise 是用于处理异步操作的一种方式。它代表了一个 可能 在将来某个时间点完成或失败的操作的结果。Promise 使得我们能够优雅地处理异步代码,避免了回调地狱(Callback Hell)的问题,提升了代码的可读性和可维护性。
- Promise 的基本状态
- Pending(进行中): 初始状态,表示异步操作还未完成。
- Fulfilled(已完成): 表示异步操作已成功完成
- Rejected(已拒绝): 表示异步操作失败
二、Promise 的基本用法
- 创建 Promise
Promise 是通过构造函数创建的,new Promise() 接受一个执行器函数作为参数,该函数有两个参数:resolve 和 reject,分别用于处理成功和失败的情况
let promise = new Promise((resolve, reject) => {
// 异步操作
let success = true; // 假设这是异步操作的结果
if (success) {
resolve("操作成功");
} else {
reject("操作失败");
}
});
-
使用 then() 和 catch() 处理结果
- then() 用于处理成功的结果。
- catch() 用于处理失败的结果。
promise
.then(result => {
console.log(result); // 输出:操作成功
})
.catch(error => {
console.log(error); // 输出:操作失败
});
三、Promise 链式调用
let promise = new Promise((resolve, reject) => {
resolve(1);
});
promise
.then(result => {
console.log(result); // 输出:1
return result + 1; // 返回新的值
})
.then(result => {
console.log(result); // 输出:2
return result + 2;
})
.then(result => {
console.log(result); // 输出:4
});
四、深入挖掘:Promise 的实现原理
-
Promise 的状态机
-
Promise 的状态是由内部的状态机管理的,状态一旦改变就不能再改变。状态流转的规则如下
- 从 Pending(进行中) 到 Fulfilled(已完成):当异步操作成功时,调用 resolve() 方法,Promise 进入 Fulfilled 状态。
- 从 Pending(进行中) 到 Rejected(已拒绝):当异步操作失败时,调用 reject() 方法,Promise 进入 Rejected 状态。
-
状态的转变是 不可逆的,即一旦从 Pending 状态变成了 Fulfilled 或 Rejected,就不能再回到 Pending 状态。
-
Promise 的微任务队列
在 JavaScript 的事件循环(Event Loop)中,Promise 的回调函数(即 then() 或 catch() 中的回调函数)被放入 微任务队列(Microtask Queue) 中,优先于宏任务队列(比如 setTimeout() 和 setInterval())执行。
- 当同步代码执行完毕后,事件循环会检查微任务队列。
- 微任务队列中的任务会被优先执行,直到队列为空,然后才会继续处理宏任务队列。
console.log("同步任务 1"); let promise = new Promise((resolve, reject) => { resolve("异步任务 1"); }); promise.then(result => { console.log(result); // 输出:异步任务 1 }); console.log("同步任务 2"); // 输出顺序:同步任务 1 -> 同步任务 2 -> 异步任务 1
-
Promise 解决并发问题:Promise.all 和 Promise.race
-
Promise.all()
接受一个 Promise 对象的数组,并返回一个新的 Promise。当所有传入的 Promise 都成功完成时,返回的 Promise 也会成功,并将所有 Promise 的结果作为一个数组返回;如果任何一个 Promise 失败,返回的 Promise 会立即失败,并以第一个失败的 Promise 的错误信息为失败原因
let p1 = new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p1')); let p2 = new Promise((resolve, reject) => setTimeout(resolve, 2000, 'p2')); let p3 = new Promise((resolve, reject) => setTimeout(resolve, 3000, 'p3')); Promise.all([p1, p2, p3]) .then(results => { console.log(results); // 输出:['p1', 'p2', 'p3'] }) .catch(error => { console.log(error); });
注意事项: Promise.all() 会以第一个失败的 Promise 的错误信息为失败原因,而不是所有失败的错误信息。如果需要处理每个 Promise 的错误,可能需要使用其他方法或结合 Promise.allSettled()。
-
Promise.allSettled()
用于并行执行多个Promise对象,并在所有Promise对象都完成(无论是成功还是失败)后返回一个包含所有Promise结果的数组。与Promise.all不同的是,Promise.allSettled不会在遇到第一个失败的Promise时立即返回,而是会等待所有Promise都完成后返回结果
-
返回值
-
status:表示Promise的状态,可能的值为“fulfilled”(已解决)或“rejected”(已拒绝)
-
value:如果Promise已解决,则为解决值;如果Promise已拒绝,则为拒绝原因
const promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('Promise 1 resolved'); }, 2000); }); const promise2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('Promise 2 resolved'); }, 1000); }); const promise3 = new Promise((resolve, reject) => { setTimeout(() => { reject('Promise 3 rejected'); }, 1500); }); Promise.allSettled([promise1, promise2, promise3]) .then(results => { results.forEach(result => { if (result.status === 'fulfilled') { console.log('Resolved:', result.value); } else if (result.status === 'rejected') { console.log('Rejected:', result.reason); } }); });
-
-
-
Promise.race()
用于解决一组 Promise 中最早解决或拒绝的 Promise。它接受一个 Promise 数组或可迭代对象作为参数,返回一个新的 Promise 对象。这个新返回的 Promise 会采用第一个完成(无论是解决还是拒绝)的 Promise 的状态和结果。
-
工作原理
- 参数:接受一个 Promise 数组或可迭代对象作为参数。
- 行为:返回一个新的 Promise 对象。一旦传入的 Promise 数组中的任何一个 Promise 解决或拒绝,返回的 Promise 就会采用那个 Promise 的状态和结果。
- 状态:如果第一个完成的 Promise 是解决状态,返回的 Promise 也是解决状态;如果是拒绝状态,返回的 Promise 也是拒绝状态12。
-
使用场景
- 竞争条件:当多个异步操作可能同时完成,但你只需要第一个完成的结果时。
- 超时处理:可以将一个超时操作与实际的操作放在同一个 race 中,确保在超时后能够及时处理。
- 事件监听:在多个事件监听器中,只需要第一个触发的事件的处理结果。
// 定义两个异步操作 const p1 = new Promise((resolve, reject) => setTimeout(() => resolve('p1'), 500)); const p2 = new Promise((resolve, reject) => setTimeout(() => reject('p2'), 300)); // 使用 Promise.race() Promise.race([p1, p2]).then((result) => { console.log(result); }).catch((error) => { console.log(error); // 'p2',因为 p2 先拒绝 }); // 在这个例子中,p2 是第一个被拒绝的 Promise,因此 catch() 被调用并打印 'p2'。
-
-
五、为什么要使用 Promise?
- 避免回调地狱:传统的回调函数(Callback)可能会出现嵌套问题,导致代码难以理解和维护。使用 Promise 可以避免嵌套回调。
- 更好的错误处理:Promise 使得错误处理变得简单,使用 catch() 可以统一处理异常。
- 链式调用:多个异步操作可以通过链式调用的方式实现顺序执行。
- 并发操作管理:通过 Promise.all() 或 Promise.race() 可以更方便地管理多个异步任务的并发执行。