文接上回,我们来讲讲Promise,在讲Promise之前,我们先来讲另一个概念,同步和异步。
同步的特点:
-
通常情况代码都是自上向下一行一行执行的
-
前边的代码不执行后边的代码也不会执行
-
同步的代码执行会出现阻塞的情况
-
一行代码执行慢会影响到整个程序的执行
为了解决这个问题,我们引入了一个新概念:异步,显著特点就是 一段代码的执行不会影响到其他的程序。但是又遇到了异步的问题:异步的代码无法通过return来设置返回值
异步的特点:
-
不会阻塞其他代码的执行
-
需要通过回调函数来返回结果
基于回调函数的异步带来的问题:
-
代码的可读性差
-
可调试性差
但是呢,当我们进行一些复杂的调用的时,会出现“回调地狱”,而Promise可以帮助我们解决异步中的回调函数的问题,Promise就是一个用来存储数据的容器
Promise是一个可以用来存储数据的对象,它存储数据的方式比较特殊,这种特殊方式使得它可以用来存储异步调用的数据。
一、Promise
1.Promise的状态
接下来讲讲Promise的状态:
-
pending (进行中)
-
fulfilled(完成) 通过resolve存储数据时
-
rejected(拒绝,出错了) 出错了或通过reject存储数据时
注意:一个 Promise的状态
只能从 Pending 转变为 Fulfilled 或 Rejected,且一旦确定状态,状态不会改变。
const myPromise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true; // 假设这是异步操作的结果
if (success) {
resolve("操作成功"); // 将 Promise 状态改为 Fulfilled,并传入值
} else {
reject("操作失败"); // 将 Promise 状态改为 Rejected,并传入错误信息
}
}, 1000);
});
2.处理 Promise的方法
Promise
提供了 .then()
和 .catch()
方法来处理异步操作的结果:
-
.then()
:用于处理Promise
成功的结果。它接收两个参数:-
第一个参数是
Promise
成功时的回调函数。 -
第二个参数是可选的,用于处理
Promise
失败时的回调函数。
-
-
.catch()专门用于处理
Promise
失败的情况。它接收一个回调函数,用于处理错误。 -
.finally()无论是正常存储数据,还是出现异常,finally总会执行。
myPromise
.then((result) => {
console.log("成功:", result); // 输出:成功:操作成功
}, (error) => {
console.log("失败:", error);
})
.catch((error) => {
console.log("捕获错误:", error);
});
.finally(()=>{
console.log("无论如何都输出")
})
当我们通过then读取数据时,相当于为Promise设置了回调函数,如果PromiseState变为fulfilled,则调用then的第一个回调函数来返回数据,如果PromiseState变为rejected,则调用then的第二个回调函数来返回数据。
3.Promise 的常见方法
(1)Promise.all()
Promise.all() 同时返回多个Promise的执行结果,其中有一个报错,就返回错误
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => resolve(3), 1000);
});
Promise.all([promise1, promise2, promise3])
.then((results) => {
console.log(results); // 输出:[1, 2, 3]
})
.catch((error) => {
console.log("捕获错误:", error);
});
(2)Promise.race()
返回执行最快的Promise(不考虑对错)
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve("第一个完成"), 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => reject("第二个失败"), 500);
});
Promise.race([promise1, promise2])
.then((result) => {
console.log("成功:", result);
})
.catch((error) => {
console.log("捕获错误:", error); // 输出:捕获错误:第二个失败
});
(3)Promise.resolve()
创建一个立即完成的Promise
const value = "直接值";
const resolvedPromise = Promise.resolve(value);
resolvedPromise
.then((result) => {
console.log(result); // 输出:直接值
});
(4) Promise.reject()
创建一个立即拒绝的Promise
const rejectedPromise = Promise.reject("出错了");
rejectedPromise
.catch((error) => {
console.log("捕获错误:", error); // 输出:捕获错误:出错了
});
(5)Promise.any()
返回执行最快的完成的Promise
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => reject("Promise 1 失败"), 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => resolve("Promise 2 成功"), 2000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => reject("Promise 3 失败"), 1500);
});
Promise.any([promise1, promise2, promise3])
.then((result) => {
console.log("第一个成功的结果:", result); // 输出:第一个成功的结果: Promise 2 成功
})
.catch((error) => {
console.log("所有 Promise 都失败了:", error);
});
接下来我们继续深挖,Promise在代码中的执行顺序是怎样的呢?
二、宏任务和微任务
在JS中任务队列有两种,这里我们就谈到了宏任务和微任务:
宏任务 是那些在事件循环的每一“轮”中执行的任务。它们通常需要等待更长的时间才能执行,因为它们会被放入到事件循环的“宏任务队列”中。
常见的宏任务有:
-
setTimeout
和setInterval
:这些函数会在指定的延迟时间后执行回调函数。 -
setImmediate
(仅在 Node.js 中可用):在当前事件循环结束后的下一次事件循环中执行回调。
微任务 是那些在当前任务执行完成后,但在事件循环进入下一轮之前执行的任务。它们会被放入到“微任务队列”中,并且会在当前任务执行完成后立即执行。常见的微任务包括:
-
Promise
的回调:Promise
的.then()
、.catch()
和.finally()
方法中的回调函数。 -
queueMicrotask()
:这是一个专门用于将任务放入微任务队列的函数。
那么宏任务和微任务的执行顺序是怎样的呢?
没错,就是事件循环机制:
-
执行同步代码:
-
事件循环从宏任务队列中取出一个任务执行。
-
在执行过程中,可能会遇到微任务(如
Promise
的回调),这些微任务会被放入微任务队列。
-
-
执行微任务队列中的任务:
-
当当前宏任务执行完成后,事件循环会立即执行微任务队列中的所有任务,直到微任务队列为空。
-
微任务的执行顺序是先进先出(FIFO)。
-
-
进入下一轮事件循环:
-
当微任务队列为空时,事件循环进入下一轮,从宏任务队列中取出下一个任务执行。
-
如果宏任务队列为空,事件循环会等待新的宏任务进入队列。
-
举个栗子:
console.log("1");
setTimeout(() => {
console.log("2");
Promise.resolve().then(() => {
console.log("3");
});
}, 0);
Promise.resolve().then(() => {
console.log("4");
});
console.log("5");
以下是解释:
-
执行同步代码,打印
1
。 -
将
setTimeout
的回调放入宏任务队列。 -
将
Promise
的回调放入微任务队列。 -
执行同步代码,打印
5
。 -
当当前执行栈为空时,事件循环执行微任务队列中的任务,打印
4
。 -
微任务队列为空,事件循环进入下一轮,从宏任务队列中取出
setTimeout
的回调并执行,打印2
。 -
在
setTimeout
的回调执行过程中,又将一个Promise
的回调放入微任务队列。 -
当
setTimeout
的回调执行完成后,事件循环执行微任务队列中的任务,打印3
。