我要學會 JS(三):callback、Promise 和 async/await 那些事兒
年前有個朋友面試,遇到了同步和非同步的問題。這幾年 JavaScript 也從 callback,慢慢演進到許多非同步的解法。
年前有個朋友面試,遇到了同步和非同步的問題。這幾年 JavaScript 也從 callback,慢慢演進到許多非同步的解法。
之前有提過 JavaScript 是一門有點 FP(Functional Progrmming)的語言,而 FP 把函式當成 first class(一等公民),所有的 function 都可以當成一種物件、當成一種參數來傳遞。像是你可以寫出這樣子的程式碼:
const onSuccess = (res) => {
if (res.result === 0) {
console.log('登入成功');
} else {
console.log('登入失敗');
}
};
login('username', 'password', onSuccess);
}
但你可能會比較習慣 callback
的形式,直接把那個 function 放在參數裡面,不另行宣告:
login('username', 'password', (res) => {
if (res.result === 0) {
console.log('登入成功');
} else {
console.log('登入失敗');
}
});
callback
是個很棒的寫法,可以很容易地看出哪個 function 執行完之後要做什麼事情。不過 callback
一多,縮排就會很醜:
login('username', 'password', (res) => {
if (res.result === 0) {
getPosts(res.uid, (posts) => {
if (posts && posts.length) {
console.log('你的文章有 ' + posts.length + ' 篇');
} else {
console.log('沒有文章');
}
});
} else {
console.log('登入失敗');
}
});
然後再多幾個 callback
就會變成龜派氣功了。這就是俗稱的 Callback Hell。
這裡(callbackhell.com) 有幾個做法教你如何避免 Callback Hell,像是宣告好函式然後拆開使用之類的。不過接下來要講的 Promise
,才是 JS 試圖解決 Callback Hell 的方法。
Promise
有了 Promise
以後,你就可以把剛剛那段 code 改寫成這樣:
loginAsync('username', 'password')
.then((res) => {
if (res.result === 0)
return getPostAsync(res.uid);
return console.log('登入錯誤');
})
.then((posts) => {
if (posts && posts.length) {
console.log('你的文章有 ' + posts.length + ' 篇');
} else {
console.log('沒有文章');
}
});
可以像發動遊戲王卡的陷阱卡一樣一步一步串(chain)起來,只要用一個 Promise
把原先的動作包起來就好了。
而寫一個簡單的 Promise
可以這樣做:
const logAsync = (message, time) => {
return new Promise((resolve, reject) => {
if (message && time) {
setTimeout(() => {
console.log(message);
resolve()
}, time);
} else {
reject();
}
});
};
主要是回傳一個 Promise
,Promise
裡面放著要執行的 function
,然後成功的話呼叫 resolve
方法、失敗的話呼叫 reject
方法。這樣就可以把方法 chain 起來:
logAsync('這個訊息過一秒才會出現', 1000)
.then(() => {
return logAsync('這個訊息再過 1.5 秒才會出現', 1500);
})
.then(() => {
return logAsync('這個訊息再過 2 秒才會出現', 2000);
});
原本就存在的 function
可以以類似的方法用 Promise
包起來,這樣就能用 then
的方式連續呼叫多個方法了。
Promise
其實還有不少用法,之後有機會再開番外篇來介紹。這篇提 Promise
主要是為了後面的 async
/await
鋪路,這才是 JS 非同步潮的地方。
你可能還聽過
generator
,但現在其實很少用到這東西了。建議你學async
/await
就好了。
Async/await
async function
是不管怎樣都會回傳 Promise
的函式。例如:
const foo = async () => {
return 1;
}
foo().then((res) => {
console.log(res);
});
雖然我們的 foo
回傳的不是一個 Promise
,但因為它是 async function
的關係,JS 會自動把它包成 Promise
,所以可以使用 then
,結果會得到 1。
而 await
則是可以等 Promise
執行完再執行下一行:
const demo = async () => {
await logAsync('1 秒後會出現這句', 1000);
await logAsync('再 1.5 秒後會出現這句', 1500);
await logAsync('再 2 秒後會出現這句', 2000);
};
demo();
不過 await
必須在 async function
裡面才能使用。
而且 await
也能夠把 Promise
回傳的值接起來,通常我們在呼叫 API(例如執行 fetch
、axios
)的時候就很好用:
(async () => {
const res = await fetch('API_URL');
const data = await res.text();
console.log(data);
})();
搭配 axios
更可以這樣使用:
((async () => {
const { data } = await axios.get('API_URL');
console.log(data);
})();
結語
使用 async
/await
呼叫 API 或是其他非同步方法,不但可以避免 Callback Hell,比起 Promise
更增加了程式可讀性(這點見仁見智就是了),是個我覺得寫 JS 的朋友都應該會的東西。
最後,我覺得如果第一次碰 async
/await
,還是多看幾篇文章,看看不同人的觀點。我第一次碰這東西也是寫的霧煞煞,所以推薦幾篇文章:
- 鐵人賽:JavaScript Await 與 Async
- 告別 JavaScript 的 Promise!迎接 Async/Await 的到來
- How To Master Async/Await With This Real World Example
第一篇先到這邊,下一篇來講運算子、選擇結構和函式。
我要學會 JS 目錄
- 我要學會 JS(一):JavaScript 簡介
- 我要學會 JS(二):基本運算與結構
- 我要學會 JS(三):callback、Promise 和 async/await 那些事兒
- 待續...