1. 回调地域
在学习 Promise 之前,我们先看一下什么是回调地狱:
这里我们模拟三个请求接口:
- 获取产品类别
// 1. 获取类别信息
const getCategory = () => {
// 模拟请求
let res = {
code: 200,
message: "请求成功",
data: [
{ id: 1, name: "水果" },
{ id: 2, name: "图书" },
],
};
return res;
};
- 根据类别 id 获取产品信息
// 2. 根据类别 id 获取产品信息
const getProductByCategoryId = (categoryId) => {
// 模拟请求
let res = {
code: 200,
message: "请求成功",
data: [
{ id: 111, categoryId: 1, name: "苹果" },
{ id: 112, categoryId: 1, name: "香蕉" },
{ id: 113, categoryId: 1, name: "百香果" },
],
};
return res;
};
- 根据产品 id 获取产品价格
// 3. 根据产品id获取产品价格
const getPriceByProductId = (productId) => {
// 模拟请求
let res = {
code: 200,
message: "请求成功",
data: { id: 1001, productId: 111, price: 112.23, unitPrice: 1235.23 },
};
return res;
};
接下来我们去获取第一个类别下第一个产品的单位价格信息:
let unitPrice = 0.0;
// 获取产品价格
const getProductPrice = () => {
let categoryRes = getCategory();
if (categoryRes.code === 200) {
let produceRes = getProductByCategoryId(1);
if (produceRes.code === 200) {
let priceRes = getPriceByProductId(1);
if (priceRes.code === 200) {
unitPrice = res.data.unitPrice;
}
}
}
};
我们发现在 getProductPrice 这个方法中,第一个请求接口的结果要作为第二个请求接口的参数,以此类推就会出现层层嵌套,回调里面套回调,这就是所谓的“回调地狱”。
如果请求的接口太多,那代码写起来可就太苦逼了,就跟套娃一样。
所以为了解决回调地狱的问题,提高代码的可读性,Promise 应运而生。
2. 邂逅 Promise
Promise 是异步编程的一种解决方案。它本质上是一个构造函数,可以构建一个 Promise 对象。
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject 。对象上有 then、catch、finall 方法。
Promise 有三种状态:pending、fulfilled、rejected:
- pending:它的意思是待定的,相当于是一个初始状态。创建 Promise 对象时就是一个初始状态。
- fulfilled:成功。当调用 resolved 方法后,Promise 对象的状态就会从 pending 切换到 fulfilled,且不可改变。
- rejected:失败。当调用 reject 方法后,Promise 对象的状态就会从 pending 切换到 reject,且不可改变。
看了上面的解释,你可能还是有点懵逼,接下来我用通俗易懂的语言解释一下 Promise 是个什么玩意:
1.Promise 就是一个用来封装 HTTP 请求的工具。
2.我们请求后台接口获取数据,如果请求成功,就调用 Promise 的 resolve 方法,并将返回的数据作为该方法的参数。如果请求失败,就调用 Promise 的 reject 方法,并将返回的错误信息作为该方法的参数。
3.然后我们将一个 Promise 对象作为该请求接口的返回值返回。
4.因为该接口返回一个 Promise 对象,所以我们调用该接口的时候就可以直接用.then()处理成功的信息,用 .catch() 处理失败的信息了。
接下来我们将上面的例子用 Promise 改造一下,有了真实案例,大家对 Promise 的理解就更清晰明了了。
3. 使用 Promise
- 获取类别信息
const getCategory = () => {
// 返回结果封装成 Promise 对象
return new Promise((resolve, reject) => {
// 模拟请求
let res = {
code: 200,
message: "请求成功",
data: [
{ id: 1, name: "水果" }
],
};
if (res.code == 200) {
resolve(res);
} else {
reject(res);
}
});
};
- 根据类别 id 获取产品信息
const getProductByCategoryId = (categoryId) => {
return new Promise((resolve, reject) => {
// 模拟请求
let res = {
code: 200,
message: "请求成功",
data: [
{ id: 111, categoryId: 1, name: "苹果" },
{ id: 112, categoryId: 1, name: "香蕉" },
{ id: 113, categoryId: 1, name: "百香果" },
],
};
if (res.code == 200) {
resolve(res);
} else {
reject(res);
}
});
};
- 根据产品 id 获取产品价格
const getPriceByProductId = (productId) => {
return new Promise((resolve, reject) => {
// 模拟请求
let res = {
code: 200,
message: "请求成功",
data: { id: 1001, productId: 111, price: 112.23, unitPrice: 1235.23 },
};
if (res.code == 200) {
resolve(res);
} else {
reject(res);
}
});
};
- 获取第一个类别下第一个产品的单位价格信息
Promise 最常用的就是链式调用格式:
let unitPrice = 0.0;
// 获取产品价格
const getProductPrice = () => {
getCategory().then(res => {
// 类别 id
let id = res.data[0].id;
// 返回一个 Promise 对象
return getProductByCategoryId(id);
}).then(res => {
// 产品 id
let id = res.data[0].id;
return getPriceByProductId(id);
}).then(res => {
unitPrice = res.data.unitPrice;
})
};
当然我们在日常使用过程中一般都是这种格式:
getMethod().then(res => {
// 请求成功
}).catch(error=>{
// 异常
}).finally(()=>{
// 不管成功还是异常都要执行
})
4. async 和 await
虽然有了 Promise 之后,代码的可读性有了很大提高。但是 ES7 又引入了 async 和 await 来简化 Promise 调用操作,实现了以异步操作像同步的方式去执行。
说白了async 和 await 就是对 Promise 进行了封装。
语法:
await 和 async 是成对出现的,如果写了 await 必须要写 async,否则会报错。如果只写 async,那返回的就是一个 Promise 对象
举例:
let unitPrice = 0.0;
// 获取产品价格
const getProductPrice = async () => {
let res1 = await getCategory();
let categoryId = res1.data[0].id;
let re2 = await getProductByCategoryId(categoryId);
let productId = re2.data[0].id;
let re3 = await getPriceByProductId(productId);
unitPrice = res3.data.unitPrice;
};
如果只写 async,返回的就是一个 Promise 对象
const getProductPrice = async () => {
getCategory().then(res=>{
let categoryId = res.data[0].id
})
};
const getCategory = async () => {
// 模拟请求
let res = {
code: 200,
message: "请求成功",
data: [
{ id: 1, name: "水果" },
{ id: 2, name: "图书" },
],
};
return res;
};
那为什么说 async 和 await 实现了异步编程同步化呢?
因为 await 这个命令的意思就是等这一行的异步方法执行成功后,然后才能执行下一行代码,否则就一直等待,下面的代码就执行不了。
所以虽然请求后台的接口是异步的,但是 await 在语法层面实现了同步。
5. 答疑
5.1 同步请求和异步请求
同步请求:当发送一个同步请求时,会暂停后面的代码执行,等待请求的返回结果,然后再继续执行下一行代码。
异步请求:当发送一个异步请求时,会继续执行后面的代码而不会等待请求的返回结果。当请求完成后,再通过回调函数或事件处理函数来处理返回的数据。
- 同步请求就会依次打印:1、2。如果第一个方法执行时间比较长,那就一直等待。
const getProductPrice = () => {
console.log("1")
};
console.log("2")
- 异步请求可能会先打印 2,后打印 1。
const getProductPrice = async () => {
console.log("1")
};
console.log("2")
5.2 promise 和 axios 什么关系
Promise 是 JavaScript 中用于异步编程的一个对象,而 axios 是 用来发送 HTTP 请求的工具库。
Promise 对 axios 的返回结果进行了封装。所以当你发送一个 axios 请求,会返回一个 Promise 对象。然后你就可以调用 .then、.catch方法了。
5.3 promise 和 async/await 什么关系
- async/await 对 promise 进行了封装。
- async/await 是用同步语法去获取异步请求,彻底消灭回调函数。
- 只有 async,返回的是 Promise 对象。
- await 相当于 Promise 的 then