iT邦幫忙

2021 iThome 鐵人賽

DAY 20
0
Modern Web

前端藏寶圖系列 第 20

async/await 連體嬰

前言

非同步概念的最後一小塊拼圖,要來學習怎麼使用 async/await
async/await 是 ECMAScript 2017 引入的語法糖,對於遇到非同步腦袋就打結的我,async/await 簡直就是救星~
async/await 像個連體嬰一樣總是形影不離,不過它們長得還是不一樣嘛,所以還是先分開認識一下

async

如果在一般函式前加上 async 關鍵字,代表我們宣告了一個非同步函式
先來看看一般函式,和在同一個函式前加上 async 的區別

可以觀察到只要在函式前加上 async,就保證這個函式會回傳一個 Promise,即使函式的回傳值只是單純的字串或數字,也會自動用 Promise 包裹起來

await

await 會放在非同步函式前,他的作用是等待非同步函式執行完成並回傳結果,不需要像 Promise 得使用.then() 取得 resolve 的值。

//非同步函式,等待兩秒後回傳顧客點的飲料
function makeDrinks(drinks) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(drinks);
    }, 2000);
  });
}


async function sellDrinks() {
  const firstOrder = await makeDrinks("black tea");
  console.log(firstOrder);
}

sellDrinks(); // black tea
  • 要注意的是 await 必須放在 async 函式中,如果放在一般函式會報錯!
// 在一般函式內使用 await 會報錯
function sellDrinks() {
  const firstOrder = await makeDrinks("black tea");
  console.log(firstOrder);
}

sellDrinks();

//SyntaxError: await is only valid in async function

async/await 執行順序

對關鍵字有基礎認識後,用一個簡單的範例理解 async/await 的執行步驟

async function foo() {
   const result = await new Promise((resolve) => setTimeout(() => resolve('1'), 1000))
   console.log(result)
}
foo();

// 一秒過後,印出1
  1. 執行 foo 函式,函式進到第一行後,await 接的非同步函式帶有一個 pending 的 Promise,程式碼不會繼續往下執行,控制權回到呼叫 foo 的函式
  2. 一秒過後,Promise 的狀態由 pending 轉為 fulfilled,控制權重新回到 foo 函式中,resolve 的值這時就會賦值給 result
  3. 程式碼繼續往下走,印出結果
  4. 控制權會轉移到 return 表達式 ,如果沒有return,則回傳預設的undefined

不同寫法的執行時間差異

理解執行的順序後,應該就不難理解下述的例子為什麼執行時間會是4秒

function makeDrinks(drinks) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(drinks);
    }, 2000);
  });
}

async function sellDrinks() {
  console.time();
  const firstOrder = await makeDrinks("black tea");
  const secondOrder = await makeDrinks("bubble tea");
  console.log(`Here is your ${firstOrder}`);
  console.log(`Here is your ${secondOrder}`);
  console.timeEnd();
}


// Here is your black tea
// Here is your bubble tea
// default: 4.023s

下面兩個例子的執行時間大約都是兩秒左右,但 MDN 並不建議第一種做法,如果希望非同步函式同時執行的話,建議使用Promise.all()Promise.allSettled()

// 寫法1
async function sellDrinks() {
  console.time();
  const order = makeDrinks("black tea");
  const extraOrder = makeDrinks("bubble tea");
  const totalOrder = (await order) + "," + (await extraOrder);
  console.log(`Here is your ${totalOrder}`);
  console.timeEnd();
}

sellDrinks();
// Here is your black tea,bubble tea
// default: 2.029s


//寫法2
async function sellDrinks() {
  console.time();
  const totalOrder = await Promise.all([
    makeDrinks("black tea"),
    makeDrinks("bubble tea"),
  ]);
  console.log(`Here is your ${totalOrder[0]}`);
  console.log(`Here is your ${totalOrder[1]}`);
  console.timeEnd();
}

sellDrinks();

// Here is your black tea
// Here is your bubble tea
// default: 2.031s

小結

使用了 async/await 後,不僅寫非同步感覺像在寫同步,在錯誤處理方面也可以用熟悉的try...catch語法。

之前看著學姊用 async/await 覺得好羨慕啊,寫起來輕鬆又好讀,但自己要寫卻不知從何寫起,現在回頭想想大概是因為當時尚未理解非同步的整個脈絡。
慢慢從 callback 循序理解 Promise 的使用,再嘗試將 Promise 替換成 async/await 後,對於 Promise 又再理解了一些些。/images/emoticon/emoticon42.gif

參考資料:
MDN
JAVASCRIPT.INFO
Asynchronous 非同步進化順序 - Async/Await


上一篇
初探 AJAX 與 Fetch API
下一篇
上有政策 - 同源政策
系列文
前端藏寶圖30

尚未有邦友留言

立即登入留言