JavaScript ES7 Async Await

TypeScript 2.1 版宣佈 async/await 語法後

且可以轉譯 ES3/ES5 版本的消息真是太令人開心了

不過在講實務應用之前還是得先了解它到底是什麼

才不會一知半解甚至是在錯誤的觀念上去建構

前言

身為一個撰寫 Javascript 前端工程師, 不喜遇到 Callback Hell

不管是自己寫的還是前人留下的產物, 對於代碼不管在除錯還是維護上都相當嚴峻

要不斷的在其中加入 console.log 進行驗證. 後來有了 Promise 的出現把  Callback Hell 漂亮的解決了

而 ES7 Async/Await 的出現並不是取代掉 Promise, 而是讓開發者多了一項選擇讓代碼更簡潔更好維護

Async Function 是什麼 ?

使用 async 定義一個非同步的函式 - 可以執行異步操作但仍然看起來同步

async function 內部可以使用 await 表達式, 它會暫停此 async function 的執行

並且等待傳遞至表達式的 Promise 的解析, 解析完之後會回傳解析值, 並繼續此 async function 的執行

請看以下範例一

Without async

function showGitHubUser(handle) {
  const url = `https://api.github.com/users/${handle}`;
  fetch(url)
    .then(response => response.json())
    .then(user => {
      console.log(user.name);
      console.log(user.location);
    });
}

console.log('before showGitHubUser');
showGitHubUser('l7960261');
console.log('after showGitHubUser');

With async

async function showGitHubUser(handle) {
  const url = `https://api.github.com/users/${handle}`;
  const response = await fetch(url);
  const user = await response.json();
  console.log(user.name);
  console.log(user.location);
}

console.log('before showGitHubUser');
showGitHubUser('l7960261');
console.log('after showGitHubUser');

因為看起來像程式會阻塞, 所以呼叫 async function 前後都特地塞了 console.log 來驗證

實際上 await 是以非同步的方式在進行, 並不會卡住或影響事件引擎的運作

有沒有覺得很神奇, 以往是要用 Promist 的寫法

而現在用這種 async/await 寫法就能達到相同的目的

An async function always returns a Promise itself, so you can use it in other asynchronous functions

意味著當宣告 async function 時總是回傳了 AsyncFunction 物件, 而 AsyncFunction 就是個 Promise

所以可想而知能在外部操作 Promise 去調用

直接觀看範例二

Use then()

async function showGitHubUser(handle) {
  const url = `https://api.github.com/users/${handle}`;
  const response = await fetch(url);
  const user = await response.json();
  console.log(user.name);
  console.log(user.location);
}


console.log('before showGitHubUser');

showGitHubUser('l7960261')
  .then(() => {
    console.log('showGitHubUser - exectued');
  });

console.log('after showGitHubUser');

Return result in promise

async function showGitHubUser(handle) {
  const url = `https://api.github.com/users/${handle}`;
  const response = await fetch(url);
  return await response.json();
}

console.log('before showGitHubUser');

showGitHubUser('l7960261')
  .then(user => {
    console.log(user.name);
    console.log(user.location);
    console.log('showGitHubUser - exectued');
  });

console.log('after showGitHubUser');

Use await for Immediately Invoked Function Expression

class Adapter {
  async showGitHubUser(handle) {
    const url = `https://api.github.com/users/${handle}`;
    const response = await fetch(url);
    return await response.json();
  }
}

console.log('before showGitHubUser');

(async function iife() {
  const adapter = new Adapter();
  const user = await adapter.showGitHubUser('l7960261');
  console.log(user.name);
  console.log(user.location);
  console.log('showGitHubUser - exectued');
})();

console.log('after showGitHubUser');

假設讀者看到這裡已經漸漸的抓到 async/await 的要領, 小結以下

  • await 的搭配使外部調用該 function 用非同步的方式處理
  • 也可以將結果回傳讓外部以 .then(res => ...) 進行撰寫 callback function
  • 更甚外部也是以 async/await 去調用 async function 省略掉 then() 的寫法

Async Function 的例外如何處理 ?

Promise 有相對的 Reject 處理, 那在 async function 要怎麼去處理例外呢 ?

範例三

Use throw & .catch(err => {...})

async function showGitHubUser(handle) {
  const url = `https://api.github.com/users/${handle}`;
  const response = await fetch(url);
  const body = await response.json();
  
  if(response.status != 200)
    throw Error(body.message);
  
  return body;
}


console.log('before showGitHubUser');

showGitHubUser('noaccountexist')
  .then(user => {
    console.log(user.name);
    console.log(user.location);
    console.log('showGitHubUser - exectued');
  })
  .catch(err => {
    console.log(`${err}`);
  });

console.log('after showGitHubUser');

Use try catch

async function showGitHubUser(handle) {
  const url = `https://api.github.com/users/${handle}`;
  const response = await fetch(url);
  const body = await response.json();
  
  if(response.status != 200)
    throw Error(body.message);
  
  return body;
}

async function iife() {
  try {
    const user = await showGitHubUser('noaccountexist');
    console.log(user.name);
    console.log(user.location);
    console.log('showGitHubUser - exectued');
  } catch(err){
    console.log(`${err}`);
  }
}

console.log('before showGitHubUser');
iife();
console.log('after showGitHubUser');

不管你是用第一種 Promise.catch() 還是第二種 try catch 的方式去處理例外

端看外部的使用方式與情境去介定, 並不是一定要用遵循其中一種才是最佳解

如何解讀復數個 Promise by Async/Await 寫法 - 順序 or 並行

有種情境大部分的開發員都遇過, 就是 Promise Chain 或是並行觸發的 Promise

在 async/await 也是可以辦得到而且直覺好了解

直接看範例

Await Sequentially

async function fetchFromGithub(endpoint) {
  const url = `https://api.github.com/${endpoint}`;
  const response = await fetch(url);
  const body = await response.json();
  
  if(response.status != 200)
    throw Error(body.message);
  
  return body;
}

async function iife(handle) {
  const user = await fetchFromGithub(`users/${handle}`);
  const repos = await fetchFromGithub(`users/${handle}/repos`);
  
  console.log(user.name);
  console.log(`${repos.length} repos`);
}

console.log('before fetchFromGithub');
iife('l7960261');
console.log('after fetchFromGithub');

Await Concurrently

async function fetchFromGithub(endpoint) {
  const url = `https://api.github.com/${endpoint}`;
  const response = await fetch(url);
  const body = await response.json();
  
  if(response.status != 200)
    throw Error(body.message);
  
  return body;
}

async function iife(handle) {
  const [user, repos] = await Promise.all([
    fetchFromGithub(`users/${handle}`),
    fetchFromGithub(`users/${handle}/repos`)
  ]);
  
  console.log(user.name);
  console.log(`${repos.length} repos`);
}

console.log('before fetchFromGithub');
iife('l7960261');
console.log('after fetchFromGithub');

講到這裡我大膽猜測讀者們已經在思考現有專案上的 legacy code 如何修改得更加優雅

相關連結

async function - JavaScript | MDN

[Javascript] Promise, generator, async與ES6 « Huli's Blog

[Javascript] ES7 Async Await 聖經 – Peter Chang – Medium

Async/Await with Angular 2 – Encoded Labs

typescript - Angular 2 Http Async await - Stack Overflow

Asynchronous JavaScript with async/await - Course by @mariusschulz @eggheadio

JavaScript 好用的 async 異步函數!

Async functions - making promises friendly  |  Web  |  Google Developers

Async functions (async/await) - JavaScript - Stack Overflow