20分鐘帶你掌握JavaScript Promise和 Async/Await

轉載請註明出處:葡萄城官網,葡萄城爲開發者提供專業的開發工具、解決方案和服務,賦能開發者。
原文出處:https://www.freecodecamp.org/news/learn-promise-async-await-in-20-minutes/javascript

 

通常在開發中,查詢網絡API操做時每每是比較耗時的,這意味着可能須要一段時間的等待才能得到響應。所以,爲了不程序在請求時無響應的狀況,異步編程就成爲了開發人員的一項基本技能。java

在JavaScript中處理異步操做時,一般咱們常常會聽到 "Promise "這個概念。但要理解它的工做原理及使用方法可能會比較抽象和難以理解。編程

那麼,在本文中咱們將會經過實踐的方式讓你能更快速的理解它們的概念和用法,因此與許多傳統乾巴巴的教程都不一樣,咱們將經過如下四個示例開始:json

  • 示例1:用生日解釋Promise的基礎知識
  • 示例2:一個猜數字的遊戲
  • 示例3:從Web API中獲取國家信息
  • 示例4:從Web API中獲取一個國家的周邊國家列表

 

示例1:用生日解釋Promise基礎知識

首先,咱們先來看看Promise的基本形態是什麼樣的。api

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完成時,不管如何都會執行的狀況
})

 

基本形態介紹完成了,那麼咱們下面開始看看下面的示例吧。promise

用戶故事:個人朋友Kayo答應在兩週後在個人生日Party上爲我作一個蛋糕。網絡

若是一切順利且Kayo沒有生病的話,咱們就會得到必定數量的蛋糕,但若是Kayo生病了,咱們就沒有蛋糕了。但不論有沒有蛋糕,咱們仍然會開一個生日Party。dom

因此對於這個示例,咱們將如上的背景故事翻譯成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後。

  • 若是Kayo沒有生病,那麼咱們就以2爲參數執行resolve函數
  • 若是Kayo生病了,那麼咱們用new Error("I am sad")做爲參數執行reject。儘管您能夠將任何要拒絕的內容做爲參數傳遞,但建議將其傳遞給Error對象。

如今,由於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:一個猜數字的遊戲

基本需求:

  • 用戶能夠輸入任意數字
  • 系統從1到6中隨機生成一個數字
  • 若是用戶輸入數字等於系統隨機數,則給用戶2分
  • 若是用戶輸入數字與系統隨機數相差1,給用戶1分,不然,給用戶0分
  • 用戶想玩多久就玩多久

對於上面的需求,咱們首先建立一個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關鍵字,咱們建立了一個異步函數,在函數內的使用方法較以前有以下不一樣:

  • 和then函數不一樣,咱們只需將await關鍵字放在Promise前,就能夠直接得到結果。
  • 咱們可使用try, catch語法來代替promise中的catch方法。

下面是咱們重構後的完整代碼,供參考:  

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在處理異步操做或不是同時發生的事情時頗有用。相信在不斷的實踐中,對它的理解會越深、越強,但願這篇文章能對你們理解Promise和Async/Await帶來一些幫助。

如下是本文中使用的代碼:

https://gcdn.grapecity.com.cn/static/events/Promise-Async-Await-main.zip

相關文章
相關標籤/搜索