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); } }); };
4.获取第一个类别下第一个产品的单位价格信息
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.同步请求就会依次打印:1、2。如果第一个方法执行时间比较长,那就一直等待。
const getProductPrice = => { console.log("1")};console.log("2")
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 什么关系
1.async/await 对 promise 进行了封装。
2.async/await 是用同步语法去获取异步请求,彻底消灭回调函数。
3.只有 async,返回的是 Promise 对象。
4.await 相当于 Promise 的 then