Promise到底是個啥?你知道嗎?
1. 回調(diào)地域
在學(xué)習(xí) Promise 之前,我們先看一下什么是回調(diào)地獄:
這里我們模擬三個請求接口:
- 獲取產(chǎn)品類別
// 1. 獲取類別信息
const getCategory = () => {
// 模擬請求
let res = {
code: 200,
message: "請求成功",
data: [
{ id: 1, name: "水果" },
{ id: 2, name: "圖書" },
],
};
return res;
};
- 根據(jù)類別 id 獲取產(chǎn)品信息
// 2. 根據(jù)類別 id 獲取產(chǎn)品信息
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;
};
- 根據(jù)產(chǎn)品 id 獲取產(chǎn)品價格
// 3. 根據(jù)產(chǎn)品id獲取產(chǎn)品價格
const getPriceByProductId = (productId) => {
// 模擬請求
let res = {
code: 200,
message: "請求成功",
data: { id: 1001, productId: 111, price: 112.23, unitPrice: 1235.23 },
};
return res;
};
接下來我們?nèi)カ@取第一個類別下第一個產(chǎn)品的單位價格信息:
let unitPrice = 0.0;
// 獲取產(chǎn)品價格
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;
}
}
}
};
我們發(fā)現(xiàn)在 getProductPrice 這個方法中,第一個請求接口的結(jié)果要作為第二個請求接口的參數(shù),以此類推就會出現(xiàn)層層嵌套,回調(diào)里面套回調(diào),這就是所謂的“回調(diào)地獄”。
如果請求的接口太多,那代碼寫起來可就太苦逼了,就跟套娃一樣。
所以為了解決回調(diào)地獄的問題,提高代碼的可讀性,Promise 應(yīng)運而生。
2. 邂逅 Promise
Promise 是異步編程的一種解決方案。它本質(zhì)上是一個構(gòu)造函數(shù),可以構(gòu)建一個 Promise 對象。
Promise 構(gòu)造函數(shù)接受一個函數(shù)作為參數(shù),該函數(shù)的兩個參數(shù)分別是 resolve 和 reject 。對象上有 then、catch、finall 方法。
Promise 有三種狀態(tài):pending、fulfilled、rejected:
- pending:它的意思是待定的,相當于是一個初始狀態(tài)。創(chuàng)建 Promise 對象時就是一個初始狀態(tài)。
- fulfilled:成功。當調(diào)用 resolved 方法后,Promise 對象的狀態(tài)就會從 pending 切換到 fulfilled,且不可改變。
- rejected:失敗。當調(diào)用 reject 方法后,Promise 對象的狀態(tài)就會從 pending 切換到 reject,且不可改變。
看了上面的解釋,你可能還是有點懵逼,接下來我用通俗易懂的語言解釋一下 Promise 是個什么玩意:
1.Promise 就是一個用來封裝 HTTP 請求的工具。
2.我們請求后臺接口獲取數(shù)據(jù),如果請求成功,就調(diào)用 Promise 的 resolve 方法,并將返回的數(shù)據(jù)作為該方法的參數(shù)。如果請求失敗,就調(diào)用 Promise 的 reject 方法,并將返回的錯誤信息作為該方法的參數(shù)。
3.然后我們將一個 Promise 對象作為該請求接口的返回值返回。
4.因為該接口返回一個 Promise 對象,所以我們調(diào)用該接口的時候就可以直接用.then()處理成功的信息,用 .catch() 處理失敗的信息了。
接下來我們將上面的例子用 Promise 改造一下,有了真實案例,大家對 Promise 的理解就更清晰明了了。
3. 使用 Promise
- 獲取類別信息
const getCategory = () => {
// 返回結(jié)果封裝成 Promise 對象
return new Promise((resolve, reject) => {
// 模擬請求
let res = {
code: 200,
message: "請求成功",
data: [
{ id: 1, name: "水果" }
],
};
if (res.code == 200) {
resolve(res);
} else {
reject(res);
}
});
};
- 根據(jù)類別 id 獲取產(chǎn)品信息
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);
}
});
};
- 根據(jù)產(chǎn)品 id 獲取產(chǎn)品價格
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);
}
});
};
- 獲取第一個類別下第一個產(chǎn)品的單位價格信息
Promise 最常用的就是鏈式調(diào)用格式:
let unitPrice = 0.0;
// 獲取產(chǎn)品價格
const getProductPrice = () => {
getCategory().then(res => {
// 類別 id
let id = res.data[0].id;
// 返回一個 Promise 對象
return getProductByCategoryId(id);
}).then(res => {
// 產(chǎn)品 id
let id = res.data[0].id;
return getPriceByProductId(id);
}).then(res => {
unitPrice = res.data.unitPrice;
})
};
當然我們在日常使用過程中一般都是這種格式:
getMethod().then(res => {
// 請求成功
}).catch(error=>{
// 異常
}).finally(()=>{
// 不管成功還是異常都要執(zhí)行
})
4. async 和 await
雖然有了 Promise 之后,代碼的可讀性有了很大提高。但是 ES7 又引入了 async 和 await 來簡化 Promise 調(diào)用操作,實現(xiàn)了以異步操作像同步的方式去執(zhí)行。
說白了async 和 await 就是對 Promise 進行了封裝。
語法:
await 和 async 是成對出現(xiàn)的,如果寫了 await 必須要寫 async,否則會報錯。如果只寫 async,那返回的就是一個 Promise 對象
舉例:
let unitPrice = 0.0;
// 獲取產(chǎn)品價格
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 實現(xiàn)了異步編程同步化呢?
因為 await 這個命令的意思就是等這一行的異步方法執(zhí)行成功后,然后才能執(zhí)行下一行代碼,否則就一直等待,下面的代碼就執(zhí)行不了。
所以雖然請求后臺的接口是異步的,但是 await 在語法層面實現(xiàn)了同步。
5. 答疑
5.1 同步請求和異步請求
同步請求:當發(fā)送一個同步請求時,會暫停后面的代碼執(zhí)行,等待請求的返回結(jié)果,然后再繼續(xù)執(zhí)行下一行代碼。
異步請求:當發(fā)送一個異步請求時,會繼續(xù)執(zhí)行后面的代碼而不會等待請求的返回結(jié)果。當請求完成后,再通過回調(diào)函數(shù)或事件處理函數(shù)來處理返回的數(shù)據(jù)。
- 同步請求就會依次打?。?、2。如果第一個方法執(zhí)行時間比較長,那就一直等待。
const getProductPrice = () => {
console.log("1")
};
console.log("2")
- 異步請求可能會先打印 2,后打印 1。
const getProductPrice = async () => {
console.log("1")
};
console.log("2")
5.2 promise 和 axios 什么關(guān)系
Promise 是 JavaScript 中用于異步編程的一個對象,而 axios 是 用來發(fā)送 HTTP 請求的工具庫。
Promise 對 axios 的返回結(jié)果進行了封裝。所以當你發(fā)送一個 axios 請求,會返回一個 Promise 對象。然后你就可以調(diào)用 .then、.catch方法了。
5.3 promise 和 async/await 什么關(guān)系
- async/await 對 promise 進行了封裝。
- async/await 是用同步語法去獲取異步請求,徹底消滅回調(diào)函數(shù)。
- 只有 async,返回的是 Promise 對象。
- await 相當于 Promise 的 then