本文開始
撰寫有關 Promise 的程式碼有另外一個更輕鬆的寫法就是用 async/await
來撰寫。
Async
function
如果,我們在一個 function 前面加上 async
這個關鍵字,
a. 就代表了這個 function 一定會回傳 Promise。
b. 如果,是回傳其他不是 Promise 類型的值得話,就會自動被包裹成 resolved Promise 的樣子。
來一些簡單的範例
以下這個範例,就是會隱性的被 async 將回傳值包裹成 resolved Promise 的範例
1 2 3 4 5
| async function implicitFnc() { return 1; }
implicitFnc().then(res => console.log(res)); // 1
|
以下這個範例,就是顯性的撰寫回傳 resolved Promise 的範例
1 2 3 4 5
| async function explicitFnc() { return Promise.resolve('This is a result'); }
explicitFnc().then(res => console.log(res)); // 'This is a result'
|
Await
await 的特性就是
- 它只有在
async
的 function 底下有作用。
- 它會等到跟它對應的 Promise 完成之後,才往下進行後面的程式碼,並且它會接收到由其對應的 Promise 回傳後的結果。
來個範例
1 2 3 4 5 6 7 8 9
| async function fnc() { const promise = new Promise((resolve, reject) => { setTimeout(() => resolve("Done!!!!"), 3000) }) const result = await promise; // step 1 - 等到 promise 結束為止,才往下執行 alert(result); }
fnc();
|
上面的範例,
程式碼會在 step 1 這邊停住,直到 promise 完成之後,才往下執行,並且 promise 回傳的結果,會存在 result 裡。
雖然,fnc 這個 function 會暫停在 step 1 這一行,直到 promise 完成執行為止,但這並不代表整個 JS 都會停在這一行程式碼不執行,它還是一樣可以同一時間去執行其他的任務內容的。
Notice:
這邊再次強調, await
只有在 async
的 function 底下有作用。
如果你在不是 async
的 function 底下使用 await
是會報錯的優。
試試下面這個範例
function fnc() {
const promise = Promise.then(1);
const result = await promise; // error
}
fnc();
直接用 async…await
來改寫一下在 Promise chaining 的範例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| async function fnc() { const response = await fetch('https://randomuser.me/api/'); const responseJson = await response.json(); const userInfo = responseJson.results[0]; const img = document.createElement('img'); img.src = userInfo.picture.large; document.body.append(img); const name = await new Promise((resolve, reject) => { setTimeout(() => { const userName = Object.keys(userInfo.name).reduce((acc, crr) => { return acc += userInfo.name[`${crr}`] + ' '; }, ''); resolve(userName); }, 3000); })
img.remove(); alert(`Showing ${name}`); }
fnc();
|
Allow top-level await
in modules
在現代的瀏覽器是允許 await
關鍵字出現在 modules
的上方出現的喔。
舉下面的範例
1 2 3
| const enLangFile = fetch('../assets/i18n/en.json').then(res => res.json());
export default await enLangFile; // await enLangFile resolove
|
上面這個範例,只要有其他地方引入這個模組,都會先等 enLangFile 這個 Promise 解析完成之後,在繼續執行後續的內容。
感覺蠻方便的?
但是,若是比較舊版本的瀏覽器就沒有辦法讓我們這樣直接加 await
在 modules 上面的。
但是,我們可以改用下面的寫法,會有一樣的效果
1 2 3 4 5
| --- 改用 IIFE 來改寫 --- (async() => { const enLangFile = fetch('../assets/i18n/en.json').then(res => res.json()); export default await enLangFile; // await enLangFile resolove })()
|
await accepts Thenable
await 也可以用在某些本身具有 then method 的物件喔。
就是在 Promise chaining 那邊有提到,在某些第三方套件會用這樣的手法,來讓我們可以使用 Promise chaining 的效果。
舉個範例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Thenable { constructor(num) { this.num = num; }
then(resolve, reject) { setTimeout(() => resolve(this.num), 1000); // 在一秒後 resolve its num value } }
async function fnc() { const result = await new Thenable(2); alert(result); }
fnc();
|
上面範例的 await
就會得到一個帶有 .then
方法的 non-Promise 的物件,await 會直接去呼叫該 object 的 .then
方法(這個方法將 JS 內建的 resovle
和 reject
當成其參數,就像 Promise 那樣)。
接著,await 就會等到 resolve
和 reject
其中一個被呼叫,並解出回傳出來的結果。
在 class 裡面定義 async method
若要在 class 中定義 async
方法,就直接在該方法名稱前面加上 async
即可。
1 2 3 4 5 6 7 8 9
| class Waiter { async wait() { return await Promise.resolve(2); } }
new Waiter() .wait() .then(res => console.log(res)); // 2
|
上面的範例,因為 async
會自動地將不是 Promise 型別的結果,包裹成 Promise 型別並回傳,所以,透過 await
解析出來的結果,會透過 async
implicitly transform into Promise type.
那最後,我們再用 .then
來接由該 async
方法回傳的結果。
Error handling
當我們用 await
來接一個 呼叫 reject
的 Promise。
await 會隱性地 throw the error.
1 2 3
| async function f() { await Promise.reject(new Error("This is an error.")); }
|
上面的程式碼等同於用下面這樣寫
1 2 3
| async function f() { throw new Error("This is an error."); }
|
那也因為 await
有以上這樣的特性,我們可以搭配 try…catch
來做 error handling mechanism.
1 2 3 4 5 6 7 8 9 10
| async function f() { try { const result = await fetch("http://no-such-url"); } catch(err) { alert(err); } }
f();
|
上面的範例, catch
會接到由 await
丟出來的 error。
以上的範例,有可以改由用 .catch
來承接丟出來的錯誤。
1 2 3 4 5 6
| async function f() { const response = await fetch('http://no-suc-url'); }
// f() becomes a rejected promise f().catch(alert);
|
那如果丟出來的錯誤,沒有後續的人來承接的話,我們也可以用 unhandledrejection
這個 eventListener 來承接。
同時寫兩個錯誤的範例,第一個出現 error 的部分,會被丟到 .catch
裡,
以下面的範例 step 1 會被執行而且丟出錯誤,而 step 2 就不會被執行,
而 catch
區域接到的錯誤就是 step 1 丟出的錯誤。
1 2 3 4 5 6 7 8 9 10 11 12 13
| async function f() {
try { let response = await fetch('https://rxxxuser.me/api/?seed=1'); // step 1 丟出錯誤 let json = await response.json(); // step 2 不會被執行 } catch(err) { // catches errors both in fetch and response.json alert(err); } }
f();
|
.await
搭配 Promise.all
一起使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const urls = [ 'https://randomuser.me/api/?seed=1', 'https://randomuser.me/api/?seed=2', 'https://randomuser.me/api/?seed=3' ]
const promises = urls.map(url => fetch(url));
async function result() { const responses = await Promise.all(promises); return responses.map(async result => await result.json()); }
result().then(res => { res.forEach(item => { item.then(res => { console.log(res.results) }) }) });
|
.await
和 Promise.all
加入 try…catch
的 error handling
我們可以利用 try…catch
來承接 Promise.all
出現錯誤的狀況。
要注意的是, Promise.all
只要其中一個元素發生錯誤,其他結果會直接被忽略
範例如下
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 27
| const urls = [ 'https://randomuser.me/api/?seed=1', 'https://randomuser.me/api/?seed=2', 'http://no-such-ulr' // 這個元素會發生錯誤 ]
const promises = urls.map(url => fetch(url));
async function result() { try { const responses = await Promise.all(promises); // 會發生錯誤,並 throw error return responses.map(async result => await result.json()); } catch(err) { alert(err); return Promise.reject(new Error(err)) // 回傳出錯的內容 } }
result().then(res => { res.forEach(item => { item.then(res => { console.log(res.results) }) }) }) .catch(alert) // 傳出來的錯誤在這裡被接起來
|
Homework
Task 1
改寫成 async…await
的用法
1 2 3 4 5 6 7 8 9 10 11 12
| async function loadJson(url) { const response = await fetch(url); if(response.status === 200) { return await response.json(); } throw Error(response); }
loadJson('https://randomuser.me/api/?seed=1') .then(res => console.log(res.results[0].email)) .catch(alert)
|
Task 2
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 27 28 29
| class HttpError extends Error { constructor(response) { super(`${response.status} for ${response.url}`); this.name = name; this.response = response; } }
async function loadJson(url) { const response = await fetch(url); if(response.status === 200) { return response.json(); } throw new HttpError(response); }
async function demoUserInfo() { const name = prompt('Enter a name?'); try { const result = await loadJson(`https://no-such-url/?seed=${name}`); console.log(result.results[0].name); } catch(err) { alert(err) } }
demoUserInfo();
|
Task 3
1 2 3 4 5 6 7 8 9 10 11
| async function wait() { await new Promise(resolve => setTimeout(resolve, 1000)); return 10; }
function f() { wait().then(res => console.log(res)); }
f();
|
Reference
- https://javascript.info/async-await#await
- Example of top-level await in module