iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 21
0
Modern Web

前端常見問題攻略系列 第 21

JS async/await 系列:基礎概念篇

非同步在前端的做法不斷的在進行優化調整,先前介紹過 Promise 可以解決非同步過度巢狀的問題,而本篇要介紹的 async function(非同步函式) 及 await 則可以將非同步的程式碼寫成類似同步的形式。

Promise 與 async, await

Promise 本篇不會詳細介紹,如果不熟悉可以先查看 本篇 文章,Promise 與非同步函式兩者是密不可分的,雖然 async function 易讀性優於 Promise,但請先確保對於 Promise 有一定理解再來使用非同步函式。

本篇先建立一個基於 Promise 的函式,以下的範例都會不斷呼叫此函式來進行撰寫。

/**
 * 範例 Promise 函式
 * 
 * @param {數值:作為判斷非同步成功與否的條件} num 
 * @param {數值:非同步所執行的時間長度} [time=500] 
 * @returns {如果 num 為真則套用 resolve;失敗則套用 reject}
 */
function promiseFn(num, time = 500) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      num ? resolve(`${num}, 成功`) : reject('失敗');
    }, time);
  });
}

promise 函式呼叫時可以使用 then 來接收 resolve 的結果,當要串接兩個 promise 函式時可以使用 return 來做 "鏈接"。

promiseFn(1)
  .then(res => {
    console.log(res); // 1, 成功
    return promiseFn(2); // 鏈接第二次的 Promise 方法
  })
  .then(res => {
    console.log(res); // 2, 成功
  });

基於 Promise 的方法相當單純,就是不斷的呼叫以及使用 then 來進行鏈接,當程式碼中只有呼叫 promise 函式時不會有太大問題,但如果有其它的方法需要進行運行時,就會顯得 promise 格格不入。

Promise 相當不錯,可以解決過巢及串接 callback function 語法不一致等問題,但它依然在 JS 的同步語言中插入了一段非同步的片段。

https://ithelp.ithome.com.tw/upload/images/20201006/20083608FoV8XSX7Xz.png

Async function 及 Await

async function 可以用來定義一個非同步函式,讓這個函式本體是屬於非同步,但其內部以“同步的方式運行非同步”程式碼。

await 則是可以暫停非同步函式的運行(中止 Promise 的運行),直到非同步進入 resolvereject,當接收完回傳值後繼續非同步函式的運行。

https://ithelp.ithome.com.tw/upload/images/20201006/20083608DRGGPpzDS1.png

Promise 的回傳狀態,需要進入 resolvereject 後,非同步函式才會繼續運行

上述以 promisethen 寫法的程式碼,以非同步的程式碼改寫方式如下:

  1. 定義非同步函式(async function
  2. 透過 await 暫停 promiseFn,直到回傳後再繼續向下
async function getData() {
  const data1 = await promiseFn(1); // 因為 await,promise 函式被中止直到回傳
  const data2 = await promiseFn(2);
  console.log(data1, data2); // 1, 成功 2, 成功
}
getData();

以上這段程式碼的結果與使用 then 是一致的,但就結構上更加平整,在 getData 這個函式中都是以 "同步" 的方式運行,不會產生同步、非同步混合的狀況。

https://ithelp.ithome.com.tw/upload/images/20201006/20083608fQAc8DuoQ8.png

無論是不是 Promise,大家都是同步程式碼逐行執行。

Async / Await 語法解析

Async / Await 目的是讓程式碼的結構變得更加簡潔、易懂,所以運用上也如上述一樣單純(如果沒有 “錯誤”,它確實很單純),本段則額外補充說明這兩者新加入的語法是如何在 JS 中運作的。

async function 非同步函式

async function 的用法相當特別,用此語法所宣告的函式,可在其內以“同步的方式運行非同步”程式碼;但就名稱上 async 是稱為非同步,那麼它的 非同步 又存在哪呢?

async function asyncFn() {
  return 'a';
}
console.log(asyncFn());

非同步 稱的就是 async function 所定義的函式本體,當使用 console.log 查看 async function 那麼將可以得到與 Promise 結構相似的函式,該函式是以非同步的方式運行,無法直接使用 console.log 取得其值。

https://ithelp.ithome.com.tw/upload/images/20201006/20083608h9xe8Qx1mg.png

在 Promise 中,如果要取得 resolve 的結果會使用 then,而 async function 也是相同使用 then()

asyncFn().then(r => {
  console.log(r)
});

雖說如此,實戰中不太會這麼做,回到目的性來說:「async function 是讓函式內的語法同步執行」。也因為 async function 與一般函式定義的不同,所以請避免將所有的 function 前方都補上 async,這會產生運行及概念上不同的函式。

Await 運算子

await 是屬於一元運算子,它會直接回傳後方表達式的值;但如果是 Promise 時則會 “等待” resovle 的結果並回傳

雖然是運算子,但是在原始碼中直接運行 await 則會出現錯誤,它只能在 async function 中運行,所以 async/await 基本上是一體的,不會單獨出現。

await 1;
// Uncaught SyntaxError: await is only valid in async function

https://ithelp.ithome.com.tw/upload/images/20201006/20083608VQUMJa378p.png

比較神奇的是 await expression 卻可以直接在 Chrome console 中運行。

await 可以直接回傳後方的表達式,或者將非同步函式中的 Promise 暫停,如以下範例的 await promiseFn(2)“等待” resolve 結果回傳後,在賦予至 data2 才會回傳。

async function getData() {
  const data1 = await 1;
  const data2 = await promiseFn(2);
  console.log(data1, data2); // 1 "2, 成功"
}
getData();

本篇是 async/await 兄弟的基礎介紹,接下來的章節會持續介紹關於這兩者的錯誤流程、延伸運用等技巧。


上一篇
JS - for 迴圈與 forEach 有什麼不同
下一篇
JS async/await 系列:延伸運用篇
系列文
前端常見問題攻略30

尚未有邦友留言

立即登入留言