詳解JavaScript Promise和Async/Await
概述
一般在開發(fā)中,查詢網(wǎng)絡(luò)API操作時(shí)往往是比較耗時(shí)的,這意味著可能需要一段時(shí)間的等待才能獲得響應(yīng)。因此,為了避免程序在請(qǐng)求時(shí)無(wú)響應(yīng)的情況,異步編程就成為了開發(fā)人員的一項(xiàng)基本技能。
在JavaScript中處理異步操作時(shí),通常我們經(jīng)常會(huì)聽到 "Promise "這個(gè)概念。但要理解它的工作原理及使用方法可能會(huì)比較抽象和難以理解。
四個(gè)示例
那么,在本文中我們將會(huì)通過實(shí)踐的方式讓你能更快速的理解它們的概念和用法,所以與許多傳統(tǒng)干巴巴的教程都不同,我們將通過以下四個(gè)示例開始:
- 示例1:用生日解釋Promise的基礎(chǔ)知識(shí)
- 示例2:一個(gè)猜數(shù)字的游戲
- 示例3:從Web API中獲取國(guó)家信息
- 示例4:從Web API中獲取一個(gè)國(guó)家的周邊國(guó)家列表
示例1:用生日解釋Promise基礎(chǔ)知識(shí)
首先,我們先來(lái)看看Promise的基本形態(tài)是什么樣的。
Promise執(zhí)行時(shí)分三個(gè)狀態(tài):pending(執(zhí)行中)、fulfilled(成功)、rejected(失敗)。
new Promise(function(resolve, reject) {
if (/* 異步操作成功 */) {
resolve(value); //將Promise的狀態(tài)由padding改為fulfilled
} else {
reject(error); //將Promise的狀態(tài)由padding改為rejected
}
})
實(shí)現(xiàn)時(shí)有三個(gè)原型方法then、catch、finally
promise
.then((result) => {
//promise被接收或拒絕繼續(xù)執(zhí)行的情況
})
.catch((error) => {
//promise被拒絕的情況
})
.finally (() => {
//promise完成時(shí),無(wú)論如何都會(huì)執(zhí)行的情況
})
基本形態(tài)介紹完成了,那么我們下面開始看看下面的示例吧。
用戶故事:我的朋友Kayo答應(yīng)在兩周后在我的生日Party上為我做一個(gè)蛋糕。
如果一切順利且Kayo沒有生病的話,我們就會(huì)獲得一定數(shù)量的蛋糕,但如果Kayo生病了,我們就沒有蛋糕了。但不論有沒有蛋糕,我們?nèi)匀粫?huì)開一個(gè)生日Party。
所以對(duì)于這個(gè)示例,我們將如上的背景故事翻譯成JS代碼,首先讓我們先創(chuàng)建一個(gè)返回Promise的函數(shù)。
const onMyBirthday = (isKayoSick) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!isKayoSick) {
resolve(2);
} else {
reject(new Error("I am sad"));
}
}, 2000);
});
};
在JavaScript中,我們可以使用new Promise()創(chuàng)建一個(gè)新的Promise,它接受一個(gè)參數(shù)為:(resolve,reject)=>{} 的函數(shù)。
在此函數(shù)中,resolve和reject是默認(rèn)提供的回調(diào)函數(shù)。讓我們仔細(xì)看看上面的代碼。
當(dāng)我們運(yùn)行onMyBirthday函數(shù)2000ms后。
- 如果Kayo沒有生病,那么我們就以2為參數(shù)執(zhí)行resolve函數(shù)
- 如果Kayo生病了,那么我們用new Error("I am sad")作為參數(shù)執(zhí)行reject。盡管您可以將任何要拒絕的內(nèi)容作為參數(shù)傳遞,但建議將其傳遞給Error對(duì)象。
現(xiàn)在,因?yàn)閛nMyBirthday()返回的是一個(gè)Promise,我們可以訪問then、catch和finally方法。我們還可以訪問早些時(shí)候在then和catch中使用傳遞給resolve和reject的參數(shù)。
讓我們通過如下代碼來(lái)理解概念
如果Kayo沒有生病
onMyBirthday(false)
.then((result) => {
console.log(`I have ${result} cakes`); // 控制臺(tái)打印“I have 2 cakes”
})
.catch((error) => {
console.log(error); // 不執(zhí)行
})
.finally(() => {
console.log("Party"); // 控制臺(tái)打印“Party”
});
如果Kayo生病
onMyBirthday(true)
.then((result) => {
console.log(`I have ${result} cakes`); // 不執(zhí)行
})
.catch((error) => {
console.log(error); // 控制臺(tái)打印“我很難過”
})
.finally(() => {
console.log("Party"); // 控制臺(tái)打印“Party”
});
相信通過這個(gè)例子你能了解Promise的基本概念。
示例2:一個(gè)猜數(shù)字的游戲
基本需求:
- 用戶可以輸入任意數(shù)字
- 系統(tǒng)從1到6中隨機(jī)生成一個(gè)數(shù)字
- 如果用戶輸入數(shù)字等于系統(tǒng)隨機(jī)數(shù),則給用戶2分
- 如果用戶輸入數(shù)字與系統(tǒng)隨機(jī)數(shù)相差1,給用戶1分,否則,給用戶0分
- 用戶想玩多久就玩多久
對(duì)于上面的需求,我們首先創(chuàng)建一個(gè)enterNumber函數(shù)并返回一個(gè)Promise:
const enterNumber = () => {
return new Promise((resolve, reject) => {
// 從這開始編碼
});
};
我們要做的第一件事是向用戶索要一個(gè)數(shù)字,并在1到6之間隨機(jī)選擇一個(gè)數(shù)字:
const enterNumber = () => {
return new Promise((resolve, reject) => {
const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // 向用戶索要一個(gè)數(shù)字
const randomNumber = Math.floor(Math.random() * 6 + 1); // 選擇一個(gè)從1到6的隨機(jī)數(shù)
});
};
當(dāng)用戶輸入一個(gè)不是數(shù)字的值。這種情況下,我們調(diào)用reject函數(shù),并拋出錯(cuò)誤:
const enterNumber = () => {
return new Promise((resolve, reject) => {
const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // 向用戶索要一個(gè)數(shù)字
const randomNumber = Math.floor(Math.random() * 6 + 1); //選擇一個(gè)從1到6的隨機(jī)數(shù)
if (isNaN(userNumber)) {
reject(new Error("Wrong Input Type")); // 當(dāng)用戶輸入的值非數(shù)字,拋出異常并調(diào)用reject函數(shù)
}
});
};
下面,我們需要檢查userNumber是否等于RanomNumber,如果相等,我們給用戶2分,然后我們可以執(zhí)行resolve函數(shù)來(lái)傳遞一個(gè)object { points: 2, randomNumber } 對(duì)象。
如果userNumber與randomNumber相差1,那么我們給用戶1分。否則,我們給用戶0分。
return new Promise((resolve, reject) => {
const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // 向用戶索要一個(gè)數(shù)字
const randomNumber = Math.floor(Math.random() * 6 + 1); // 選擇一個(gè)從1到6的隨機(jī)數(shù)
if (isNaN(userNumber)) {
reject(new Error("Wrong Input Type")); // 當(dāng)用戶輸入的值非數(shù)字,拋出異常并調(diào)用reject函數(shù)
}
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,
});
}
});
下面,讓我們?cè)賱?chuàng)建一個(gè)函數(shù)來(lái)詢問用戶是否想繼續(xù)游戲:
const continueGame = () => {
return new Promise((resolve) => {
if (window.confirm("Do you want to continue?")) { // 向用戶詢問是否要繼續(xù)游戲
resolve(true);
} else {
resolve(false);
}
});
};
為了不使游戲強(qiáng)制結(jié)束,我們創(chuàng)建的Promise沒有使用Reject回調(diào)。
下面,我們創(chuàng)建一個(gè)函數(shù)來(lái)處理猜數(shù)字邏輯:
const handleGuess = () => {
enterNumber() // 返回一個(gè)Promise對(duì)象
.then((result) => {
alert(`Dice: ${result.randomNumber}: you got ${result.points} points`); // 當(dāng)resolve運(yùn)行時(shí),我們得到用戶得分和隨機(jī)數(shù)
// 向用戶詢問是否要繼續(xù)游戲
continueGame().then((result) => {
if (result) {
handleGuess(); // If yes, 游戲繼續(xù)
} else {
alert("Game ends"); // If no, 彈出游戲結(jié)束框
}
});
})
.catch((error) => alert(error));
};
handleGuess(); // 執(zhí)行handleGuess 函數(shù)
在這當(dāng)我們調(diào)用handleGuess函數(shù)時(shí),enterNumber()返回一個(gè)Promise對(duì)象。
如果Promise狀態(tài)為resolved,我們就調(diào)用then方法,向用戶告知競(jìng)猜結(jié)果與得分,并向用戶詢問是否要繼續(xù)游戲。
如果Promise狀態(tài)為rejected,我們將顯示一條用戶輸入錯(cuò)誤的信息。
不過,這樣的代碼雖然能解決問題,但讀起來(lái)還是有點(diǎn)困難。讓我們后面將使用async/await 對(duì)hanldeGuess進(jìn)行重構(gòu)。
網(wǎng)上對(duì)于 async/await 的解釋已經(jīng)很多了,在這我想用一個(gè)簡(jiǎn)單概括的說(shuō)法來(lái)解釋:async/await就是可以把復(fù)雜難懂的異步代碼變成類同步語(yǔ)法的語(yǔ)法糖。
下面開始看重構(gòu)后代碼吧:
const handleGuess = async () => {
try {
const result = await enterNumber(); // 代替then方法,我們只需將await放在promise前,就可以直接獲得結(jié)果
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函數(shù)來(lái)替代
alert(error);
}
};
通過在函數(shù)前使用async關(guān)鍵字,我們創(chuàng)建了一個(gè)異步函數(shù),在函數(shù)內(nèi)的使用方法較之前有如下不同:
- 和then函數(shù)不同,我們只需將await關(guān)鍵字放在Promise前,就可以直接獲得結(jié)果。
- 我們可以使用try, catch語(yǔ)法來(lái)代替promise中的catch方法。
下面是我們重構(gòu)后的完整代碼,供參考:
const enterNumber = () => {
return new Promise((resolve, reject) => {
const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // 向用戶索要一個(gè)數(shù)字
const randomNumber = Math.floor(Math.random() * 6 + 1); // 系統(tǒng)隨機(jī)選取一個(gè)1-6的數(shù)字
if (isNaN(userNumber)) {
reject(new Error("Wrong Input Type")); // 如果用戶輸入非數(shù)字拋出錯(cuò)誤
}
if (userNumber === randomNumber) { // 如果用戶猜數(shù)字正確,給用戶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?")) { // 向用戶詢問是否要繼續(xù)游戲
resolve(true);
} else {
resolve(false);
}
});
};
const handleGuess = async () => {
try {
const result = await enterNumber(); // await替代了then函數(shù)
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函數(shù)來(lái)替代
alert(error);
}
};
handleGuess(); // 執(zhí)行handleGuess 函數(shù)
我們已經(jīng)完成了第二個(gè)示例,接下來(lái)讓我們開始看看第三個(gè)示例。
示例3:從Web API中獲取國(guó)家信息
一般當(dāng)從API中獲取數(shù)據(jù)時(shí),開發(fā)人員會(huì)精彩使用Promises。如果在新窗口打開https://restcountries.eu/rest/v2/alpha/cn,你會(huì)看到JSON格式的國(guó)家數(shù)據(jù)。
通過使用Fetch API,我們可以很輕松的獲得數(shù)據(jù),以下是代碼:
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();
現(xiàn)在我們獲得了所需的國(guó)家/地區(qū)數(shù)據(jù),讓我們轉(zhuǎn)到最后一項(xiàng)任務(wù)。
示例4:從Web API中獲取一個(gè)國(guó)家的周邊國(guó)家列表
下面的fetchCountry函數(shù)從示例3中的api獲得國(guó)家信息,其中的參數(shù)alpha3Code 是代指該國(guó)家的國(guó)家代碼,以下是代碼
// Task 4: 獲得中國(guó)周邊的鄰國(guó)信息
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);
}
};
下面讓我們創(chuàng)建一個(gè)fetchCountryAndNeighbors函數(shù),通過傳遞cn作為alpha3code來(lái)獲取中國(guó)的信息。
const fetchCountryAndNeighbors = async () => {
const china= await fetchCountry("cn");
console.log(china);
};
fetchCountryAndNeighbors();
在控制臺(tái)中,我們看看對(duì)象內(nèi)容:

在對(duì)象中,有一個(gè)border屬性,它是中國(guó)周邊鄰國(guó)的alpha3codes列表。
現(xiàn)在,如果我們嘗試通過以下方式獲取鄰國(guó)信息。
const neighbors =china.borders.map((border) => fetchCountry(border));
neighbors是一個(gè)Promise對(duì)象的數(shù)組。
當(dāng)處理一個(gè)數(shù)組的Promise時(shí),我們需要使用Promise.all。
const fetchCountryAndNeigbors = async () => {
const china = await fetchCountry("cn");
const neighbors = await Promise.all(
china.borders.map((border) => fetchCountry(border))
);
console.log(neighbors);
};
fetchCountryAndNeigbors();
在控制臺(tái)中,我們應(yīng)該能夠看到國(guó)家/地區(qū)對(duì)象列表。

以下是示例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);
}
};
const fetchCountryAndNeigbors = async () => {
const china = await fetchCountry("cn");
const neighbors = await Promise.all(
china.borders.map((border) => fetchCountry(border))
);
console.log(neighbors);
};
fetchCountryAndNeigbors();
總結(jié)
完成這4個(gè)示例后,你可以看到Promise在處理異步操作或不是同時(shí)發(fā)生的事情時(shí)很有用。相信在不斷的實(shí)踐中,對(duì)它的理解會(huì)越深、越強(qiáng),希望這篇文章能對(duì)大家理解Promise和Async/Await帶來(lái)一些幫助。
以下是本文中使用的代碼:https://files.cnblogs.com/files/powertoolsteam/Promise-Async-Await-main.zip
以上就是詳解JavaScript Promise和Async/Await的詳細(xì)內(nèi)容,更多關(guān)于JavaScript Promise和Async/Await的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
js實(shí)現(xiàn)簡(jiǎn)單的左右兩邊固定廣告效果實(shí)例
這篇文章主要介紹了js實(shí)現(xiàn)簡(jiǎn)單的左右兩邊固定廣告效果,實(shí)例分析了javascript實(shí)現(xiàn)固定廣告的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
JS實(shí)現(xiàn)div內(nèi)部的文字或圖片自動(dòng)循環(huán)滾動(dòng)代碼
在某些情況下需要這樣的功能:使用JS實(shí)現(xiàn)div內(nèi)部的文字或圖片自動(dòng)循環(huán)滾動(dòng),接下來(lái)為大家詳細(xì)介紹下實(shí)現(xiàn)方法,感興趣的朋友可以參考下哈2013-04-04
JavaScript對(duì)象類型轉(zhuǎn)換的分類及步驟詳解
這篇文章主要為大家介紹了JavaScript對(duì)象類型轉(zhuǎn)換的分類及步驟詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
canvas實(shí)現(xiàn)弧形可拖動(dòng)進(jìn)度條效果
本篇文章主要介紹了canvas實(shí)現(xiàn)弧形可拖動(dòng)進(jìn)度條的實(shí)例方法,具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-05-05
利用JS判斷用戶是否上網(wǎng)(連接網(wǎng)絡(luò))
本篇文章主要介紹了利用JS判斷用戶是否上網(wǎng)(連接網(wǎng)絡(luò))。需要的朋友可以過來(lái)參考下,希望對(duì)大家有所幫助2013-12-12
javascript如何在foreach循環(huán)完成之后執(zhí)行一個(gè)回調(diào)函數(shù)
javascript動(dòng)態(tài)添加刪除tabs標(biāo)簽的方法

