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
Async functions - making promises friendly | Web | Google Developers