如何理解JavaScript Promise
如何理解JavaScript Promise,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
一般在開發中,查詢網絡API操作時往往是比較耗時的,這意味著可能需要一段時間的等待才能獲得響應。因此,為了避免程序在請求時無響應的情況,異步編程就成為了開發人員的一項基本技能。
在JavaScript中處理異步操作時,通常我們經常會聽到 "Promise "這個概念。但要理解它的工作原理及使用方法可能會比較抽象和難以理解。
示例1:用生日解釋Promise基礎知識
首先,我們先來看看Promise的基本形態是什么樣的。
Promise執行時分三個狀態:pending(執行中)、fulfilled(成功)、rejected(失敗)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | new
Promise(
function
(resolve, reject) {
if
(
/* 異步操作成功 */
) {
resolve(value);
//將Promise的狀態由padding改為fulfilled
}
else
{
reject(error);
//將Promise的狀態由padding改為rejected
}
})
實現時有三個原型方法then、
catch
、finally
promise
.then((result) => {
//promise被接收或拒絕繼續執行的情況
})
.
catch
((error) => {
//promise被拒絕的情況
})
.finally (() => {
//promise完成時,無論如何都會執行的情況
})
|
基本形態介紹完成了,那么我們下面開始看看下面的示例吧。
用戶故事:我的朋友Kayo答應在兩周后在我的生日Party上為我做一個蛋糕。
如果一切順利且Kayo沒有生病的話,我們就會獲得一定數量的蛋糕,但如果Kayo生病了,我們就沒有蛋糕了。但不論有沒有蛋糕,我們仍然會開一個生日Party。
所以對于這個示例,我們將如上的背景故事翻譯成JS代碼,首先讓我們先創建一個返回Promise的函數。
1 2 3 4 5 6 7 8 9 10 11 | const onMyBirthday = (isKayoSick) => {
return
new
Promise((resolve, reject) => {
setTimeout(() => {
if
(!isKayoSick) {
resolve(2);
}
else
{
reject(
new
Error(
"I am sad"
));
}
}, 2000);
});
};
|
在JavaScript中,我們可以使用new Promise()創建一個新的Promise,它接受一個參數為:(resolve,reject)=>{} 的函數。
在此函數中,resolve和reject是默認提供的回調函數。讓我們仔細看看上面的代碼。
當我們運行onMyBirthday函數2000ms后。
現在,因為onMyBirthday()返回的是一個Promise,我們可以訪問then、catch和finally方法。我們還可以訪問早些時候在then和catch中使用傳遞給resolve和reject的參數。
讓我們通過如下代碼來理解概念
如果Kayo沒有生病
1 2 3 4 5 6 7 8 9 10 | onMyBirthday(
false
)
.then((result) => {
console.log(`I have ${result} cakes`);
// 控制臺打印“I have 2 cakes”
})
.
catch
((error) => {
console.log(error);
// 不執行
})
.finally(() => {
console.log(
"Party"
);
// 控制臺打印“Party”
});
|
如果Kayo生病
1 2 3 4 5 6 7 8 9 10 | onMyBirthday(
true
)
.then((result) => {
console.log(`I have ${result} cakes`);
// 不執行
})
.
catch
((error) => {
console.log(error);
// 控制臺打印“我很難過”
})
.finally(() => {
console.log(
"Party"
);
// 控制臺打印“Party”
});
|
相信通過這個例子你能了解Promise的基本概念。
下面我們開始示例2
示例2:一個猜數字的游戲
基本需求:
對于上面的需求,我們首先創建一個enterNumber函數并返回一個Promise:
1 2 3 4 5 | const enterNumber = () => {
return
new
Promise((resolve, reject) => {
// 從這開始編碼
});
};
|
我們要做的第一件事是向用戶索要一個數字,并在1到6之間隨機選擇一個數字:
1 2 3 4 5 6 | const enterNumber = () => {
return
new
Promise((resolve, reject) => {
const userNumber = Number(window.prompt(
"Enter a number (1 - 6):"
));
// 向用戶索要一個數字
const randomNumber = Math.floor(Math.random() * 6 + 1);
// 選擇一個從1到6的隨機數
});
};
|
當用戶輸入一個不是數字的值。這種情況下,我們調用reject函數,并拋出錯誤:
1 2 3 4 5 6 7 8 9 10 | const enterNumber = () => {
return
new
Promise((resolve, reject) => {
const userNumber = Number(window.prompt(
"Enter a number (1 - 6):"
));
// 向用戶索要一個數字
const randomNumber = Math.floor(Math.random() * 6 + 1);
//選擇一個從1到6的隨機數
if
(isNaN(userNumber)) {
reject(
new
Error(
"Wrong Input Type"
));
// 當用戶輸入的值非數字,拋出異常并調用reject函數
}
});
};
|
下面,我們需要檢查userNumber是否等于RanomNumber,如果相等,我們給用戶2分,然后我們可以執行resolve函數來傳遞一個object { points: 2, randomNumber } 對象。
如果userNumber與randomNumber相差1,那么我們給用戶1分。否則,我們給用戶0分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | return
new
Promise((resolve, reject) => {
const userNumber = Number(window.prompt(
"Enter a number (1 - 6):"
));
// 向用戶索要一個數字
const randomNumber = Math.floor(Math.random() * 6 + 1);
// 選擇一個從1到6的隨機數
if
(isNaN(userNumber)) {
reject(
new
Error(
"Wrong Input Type"
));
// 當用戶輸入的值非數字,拋出異常并調用reject函數
}
if
(userNumber === randomNumber) {
// 如果相等,我們給用戶2分
resolve({
points: 2,
randomNumber,
});
}
else
if
(
userNumber === randomNumber - 1 ||
userNumber === randomNumber + 1
) {
// 如果userNumber與randomNumber相差1,那么我們給用戶1分
resolve({
points: 1,
randomNumber,
});
}
else
{
// 否則用戶得0分
resolve({
points: 0,
randomNumber,
});
}
});
|
下面,讓我們再創建一個函數來詢問用戶是否想繼續游戲:
1 2 3 4 5 6 7 8 9 | const continueGame = () => {
return
new
Promise((resolve) => {
if
(window.confirm(
"Do you want to continue?"
)) {
// 向用戶詢問是否要繼續游戲
resolve(
true
);
}
else
{
resolve(
false
);
}
});
};
|
為了不使游戲強制結束,我們創建的Promise沒有使用Reject回調。
下面,我們創建一個函數來處理猜數字邏輯:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | const handleGuess = () => {
enterNumber()
// 返回一個Promise對象
.then((result) => {
alert(`Dice: ${result.randomNumber}: you got ${result.points} points`);
// 當resolve運行時,我們得到用戶得分和隨機數
// 向用戶詢問是否要繼續游戲
continueGame().then((result) => {
if
(result) {
handleGuess();
// If yes, 游戲繼續
}
else
{
alert(
"Game ends"
);
// If no, 彈出游戲結束框
}
});
})
.
catch
((error) => alert(error));
};
handleGuess();
// 執行handleGuess 函數
|
在這當我們調用handleGuess函數時,enterNumber()返回一個Promise對象。
如果Promise狀態為resolved,我們就調用then方法,向用戶告知競猜結果與得分,并向用戶詢問是否要繼續游戲。
如果Promise狀態為rejected,我們將顯示一條用戶輸入錯誤的信息。
不過,這樣的代碼雖然能解決問題,但讀起來還是有點困難。讓我們后面將使用async/await 對hanldeGuess進行重構。
網上對于 async/await 的解釋已經很多了,在這我想用一個簡單概括的說法來解釋:async/await就是可以把復雜難懂的異步代碼變成類同步語法的語法糖。
下面開始看重構后代碼吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | const handleGuess = async () => {
try
{
const result = await enterNumber();
// 代替then方法,我們只需將await放在promise前,就可以直接獲得結果
alert(`Dice: ${result.randomNumber}: you got ${result.points} points`);
const isContinuing = await continueGame();
if
(isContinuing) {
handleGuess();
}
else
{
alert(
"Game ends"
);
}
}
catch
(error) {
// catch 方法可以由try, catch函數來替代
alert(error);
}
};
|
通過在函數前使用async關鍵字,我們創建了一個異步函數,在函數內的使用方法較之前有如下不同:
下面是我們重構后的完整代碼,供參考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | const enterNumber = () => {
return
new
Promise((resolve, reject) => {
const userNumber = Number(window.prompt(
"Enter a number (1 - 6):"
));
// 向用戶索要一個數字
const randomNumber = Math.floor(Math.random() * 6 + 1);
// 系統隨機選取一個1-6的數字
if
(isNaN(userNumber)) {
reject(
new
Error(
"Wrong Input Type"
));
// 如果用戶輸入非數字拋出錯誤
}
if
(userNumber === randomNumber) {
// 如果用戶猜數字正確,給用戶2分
resolve({
points: 2,
randomNumber,
});
}
else
if
(
userNumber === randomNumber - 1 ||
userNumber === randomNumber + 1
) {
// 如果userNumber與randomNumber相差1,那么我們給用戶1分
resolve({
points: 1,
randomNumber,
});
}
else
{
// 不正確,得0分
resolve({
points: 0,
randomNumber,
});
}
});
};
const continueGame = () => {
return
new
Promise((resolve) => {
if
(window.confirm(
"Do you want to continue?"
)) {
// 向用戶詢問是否要繼續游戲
resolve(
true
);
}
else
{
resolve(
false
);
}
});
};
const handleGuess = async () => {
try
{
const result = await enterNumber();
// await替代了then函數
alert(`Dice: ${result.randomNumber}: you got ${result.points} points`);
const isContinuing = await continueGame();
if
(isContinuing) {
handleGuess();
}
else
{
alert(
"Game ends"
);
}
}
catch
(error) {
// catch 方法可以由try, catch函數來替代
alert(error);
}
};
handleGuess();
// 執行handleGuess 函數
|
我們已經完成了第二個示例,接下來讓我們開始看看第三個示例。
示例3:從Web API中獲取國家信息
一般當從API中獲取數據時,開發人員會精彩使用Promises。如果在新窗口打開https://restcountries.eu/rest/v2/alpha/cn,你會看到JSON格式的國家數據。
通過使用Fetch API,我們可以很輕松的獲得數據,以下是代碼:
1 2 3 4 5 6 7 8 9 | const fetchData = async () => {
const res = await fetch(
"https://restcountries.eu/rest/v2/alpha/cn"
); // fetch() returns a promise, so we need to wait
for
it
const country = await res.json();
// res is now only an HTTP response, so we need to call res.json()
console.log(country);
// China's data will be logged to the dev console
};
fetchData();
|
現在我們獲得了所需的國家/地區數據,讓我們轉到最后一項任務。
示例4:從Web API中獲取一個國家的周邊國家列表
下面的fetchCountry函數從示例3中的api獲得國家信息,其中的參數alpha3Code 是代指該國家的國家代碼,以下是代碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // Task 4: 獲得中國周邊的鄰國信息
const fetchCountry = async (alpha3Code) => {
try
{
const res = await fetch(
`https:
//restcountries.eu/rest/v2/alpha/${alpha3Code}`
);
const data = await res.json();
return
data;
}
catch
(error) {
console.log(error);
}
};
|
下面讓我們創建一個fetchCountryAndNeighbors函數,通過傳遞cn作為alpha3code來獲取中國的信息。
1 2 3 4 5 6 7 | const fetchCountryAndNeighbors = async () => {
const china= await fetchCountry(
"cn"
);
console.log(china);
};
fetchCountryAndNeighbors();
|
在控制臺中,我們看看對象內容:
在對象中,有一個border屬性,它是中國周邊鄰國的alpha3codes列表。
現在,如果我們嘗試通過以下方式獲取鄰國信息。
1 2 | const neighbors =
china.borders.map((border) => fetchCountry(border));
|
neighbors是一個Promise對象的數組。
當處理一個數組的Promise時,我們需要使用Promise.all。
1 2 3 4 5 6 7 8 9 10 11 | const fetchCountryAndNeigbors = async () => {
const china = await fetchCountry(
"cn"
);
const neighbors = await Promise.all(
china.borders.map((border) => fetchCountry(border))
);
console.log(neighbors);
};
fetchCountryAndNeigbors();
|
在控制臺中,我們應該能夠看到國家/地區對象列表。
以下是示例4的所有代碼,供您參考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | const fetchCountry = async (alpha3Code) => {
try
{
const res = await fetch(
`https:
//restcountries.eu/rest/v2/alpha/${alpha3Code}`
);
const data = await res.json();
return
data;
}
catch
(error) {
console.log(error);
}
};
const fetchCountryAndNeigbors = async () => {
const china = await fetchCountry(
"cn"
);
const neighbors = await Promise.all(
china.borders.map((border) => fetchCountry(border))
);
console.log(neighbors);
};
fetchCountryAndNeigbors();
|
完成這4個示例后,你可以看到Promise在處理異步操作或不是同時發生的事情時很有用。
關于如何理解JavaScript Promise問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。