async
加在函式前面,代表這個函式一定會回傳 promise。也就是撰寫時可以在函式內明確回傳 promise,或是當回傳其他值時,該值會被包成一個 resolved promise:
async function f() {
return 1;
}
f().then(res => {
console.log(res); // 1
});
await
只能用在 async function 裡。當 await
放在 promise 前面,會等待那個 promise 完成、回傳結果。換句話說它用比較直覺的語法做原本 then
做的事情:
async function f() {
let result = await new Promise((resolve, reject) => {
setTimeout(() => resolve('done!'), 1000)
});
console.log(result); // done!
}
f();
雖然大部分時候 await
被用來等待 promise ,但它並不限定後面只能接 promise,如同 MDN 的描述:await expression
中的 expression
可以是 'A Promise, a thenable object, or any value to wait for.'。當表達式不為 promise 時,await
會把其值轉為一個 resolved promise 然後一樣等待它:
async function f3() {
const y = await 20;
console.log(y); // 20
}
f3();
雖然這樣貌似什麼事都不會發生,取到的值也沒變,但實際上還是有一些影響。因為 await
執行後 control 會回到呼叫函式的地方,而 await
以下「要等待結果的程式碼」會被放進 microtask queue 等待 (有點像 then()
無論是否已有結果都會非同步執行),所以會造成程式碼執行順序改變,像下方執行兩遍的 'end' 部分都會被往後排:
async function foo(name) {
console.log(name, "start");
await console.log(name, "middle");
console.log(name, "end");
}
foo("First");
foo("Second");
// First start
// First middle
// Second start
// Second middle
// First end
// Second end
也因為這樣的特性,建議只有在真的要等待 promise 回傳值的情境才使用 await
,以免出現意外的結果。這個問題也有類似的例子可以參考。
這裡是嘗試把之前寫到的 fetch 範例寫成 async/ await 版本,看起來已經非常像一般的同步程式碼,幾乎沒有視覺跳躍的地方,像最後 setTimeout
的部分可以把程式碼拆得更乾淨,每個步驟按照順序做單一的工作:
async function loadJson(url) {
const response = await fetch(url);
const resJson = await response.json();
return resJson;
}
async function showAvatar() {
const user = loadJson('/article/promise-chaining/user.json');
const githubUser = loadJson(`https://api.github.com/users/${user.name}`);
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 3000);
})
img.remove();
}