前言

async/await的方式提供了promise一種新的寫法,至於有沒有更方便及更高的可讀性,我覺得使用了async/await是更容易進行編寫的,至少可以不用一直.then下去,使用了async/await之後,就跟.then說再見。

今天會介紹的順序:

  1. 這兩個好用的關鍵字分別代表的意思。
  2. async/await 所達成的「同步」效果。
  3. 錯誤處理的應對措施。

async

這是一個加在function前面的關鍵字,沒有加就是普通的function,加了之後就會變成async function就會代表著現在這個function是一個非同步的function

1
2
3
4
5
6
7
8
9
// 普通的function
function add(num) {
return 10 + num;
}

// async function
async function add(num) {
return 10 + num;
}

而會說加上asyncfunction會變成非同步的原因主要是因為async會讓function在回傳的時候會返回一個promise,所以因為是promise,所以也可以使用.then來取出結果。

1
2
3
4
5
async function add(num) {
return 10 + num;
}

add(10).then(console.log); // 20

所以跟這樣也是會相同的,不加上asyncfunction

1
2
3
4
function add(num) {
return Promise.resolve(10 + num);
}
add(10).then(console.log); //20

加了asyncasync function除了可以讓整體變成非同步的function之外,還可以使用一種名為await的招式。

await

這些從它的單字意思其實就能略知一二,await是等待,在這裡當關鍵字的就是就是讓這個async function進行等待。

這邊很重要的一點就是,一定要是async function,沒有的話就沒辦法使用,可是說是有async才有await(但沒有await也可以用async)。

1
2
3
function add() {
let result = await promise;
} // SyntaxError

沒有async的狀態下直接使用await就會報錯給你看。

等待主要是讓Promise的運行先暫停一下,直到接受了回傳值才會繼續運行,而await這種藉由等待,暫停了非同步的執行的方式,是讓使用了async/awaitfunction看起來很像同步的關鍵。

明明是非同步,為什麼會看起來很像同步呢? 看過來。

async/await的「同步」

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const promise = new Promise(function (resolve) {
setTimeout(() => resolve("五秒到了"), 5000);
});

async function sec() {
console.log("在 await 之前");

let result = await promise;
console.log(result);

console.log("在 await 之後");
}

sec(); //?

在講這段程式之前,先來談談順序的問題。

一般來說,程式碼看下來的流程,會先去執行同步的程式,因為不會有需要等待排隊的問題,而非同步的因為沒辦法馬上完成,所以要等待後才能執行,等待的時間不知道,但能知道的是一定同步的東西先跑完。

程式碼的寫法順序

1
2
3
4
同步1
非同步1
同步2
同步3

實際上的執行順序:

1
2
3
4
5
6
7
同步1
同步2
同步3

...... => 確保同步都跑完

非同步1

基本上有兩個概念在執行順序這一塊:

  1. 會由上而下執行
  2. 同步先執行,才是非同步執行

回到程式碼,照著剛剛的概念來看,應該會是先跑出來在 await 之前之後是在 await 之後,而經由了setTimeout而造成需要等待幾秒才會執行的promise非同步理論上應該是最後才會跑出來,但是如果你把上面那段程式碼拿去執行,你會發現順序是:

1
2
3
await 之前
五秒到了
await 之後

怎麼跟上面講的不一樣呢?這其實就是async/await的神奇之處,使用了await就會等待,就算它本身是一個非同步的運行,在整體的async function之中,也會逐行的執行,不會有某一行被抓去排隊(事件佇列)而執行跳到下一行的狀況,看起來就可以達成是非同步的,但我用同步的方式去跑。

錯誤處理

await後面接的promise會直接回傳結果,而結果有兩種可能性。

  • 成功 => resolve
  • 失敗 => reject

在原本使用promise的時候,會使用.then.catch去獲取這兩種結果,但是async/await不用。

我研究之後發現在async/await時會使用try..catch的方式來處理錯誤的結果。

try..catch

它會分成兩個區塊,一個 try 一個 catch,主要就是正常是 try 區塊,在 try 區塊碰到錯誤,直接跳到 catch 區塊。

1
2
3
4
5
6
try {
// 要執行的程式
} catch(error) {
// ...
// 錯誤發生時,上面的就不會執行,改執行這邊
}

舉例時間:

1
2
3
4
5
try {
console.apple("apple不是正確的使用方式喔");
} catch (e) {
console.log("我錯了");
}

這段 code 的結果會印出我錯了,如下:

為什麼會發生這樣的結果?這是因為如果 try 區塊裡面的程式碼假如沒有任何的錯誤,就會直接忽略掉catch區塊裡面的程式碼,反之有錯的話,就會以catch裡面的程式碼為主,而錯誤的try就會中斷執行,所以在這個範例來說,因為try寫錯了,所以會以try裡面的程式碼為主。

另外如果catch區塊如果接受了一個參數,就可以直接利用這個參數獲取錯誤資料,一般都會是直接在後台輸出資料,這邊來試試看:

1
2
3
4
5
try {
console.apple("apple不是正確的使用方式喔");
} catch (e) {
console.log("錯誤訊息 :", e);
}

這段 code 的結果會印出錯誤資訊,如下:

async/await遇錯時

使用剛剛講的方式,先把全部的async function都用try..catch包起來,然後錯誤的話自然就會跑到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
function promise(a) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (a) {
resolve("五秒到了");
} else {
reject("錯誤發生");
}
}, 5000);
});
}

async function sec() {
try {
console.log("在 await 之前");

let result = await promise(0);
console.log(result);

console.log("在 await 之後");
} catch (error) {
console.log(error);
}
}

sec();

最後結果印出:

1
2
await 之前
錯誤發生

第一行的在 await 之前還是會出現,只是經過了下方awaitpromise,因為裡面帶入的參數是負數,所以是false,結果會跑去else reject("錯誤發生"),而最終會進到catch區塊直到結束,所以try區塊後面的在 await 之後就不會看到。

要注意一件事情,try區塊可以因為錯誤而進到catch區塊,但沒辦法反向,也就是說無法在失敗之後再度執行原本程式。

總結

async function的奇妙之處:

  1. 內部可以使用名為await的招式。
  2. 內部最終總是會回傳一個promise
  3. 整體會變成非同步的狀況。

await這個招式使用之後的兩種下場:

  1. 強制promise等待直到完成。
  2. 有錯誤的話,拋出reject的錯誤結果。
  3. 沒錯誤就直接回傳resolve的成功結果。

什麼時候可以用?

基本上當你想要使用promise.then時,都可以把它改成async/await的方式,就不用一直.then,處理非同步的程式時又多了一種寫法,看完這這篇後,大家也多嘗試用看看async/await來寫 code 吧。

reference

[1] MDN - async function
[2] MDN - await
[3] w3schools - JavaScript Async