平常使用javascript 寫網頁腳本做自動化時,因為前端會有動畫、渲染所產生的時間差問題,使得selector 在執行的當下不一定抓的到element。
為了解決此問題目前最常用的是setTimeout 跟setInterval 兩種方式,但其實有更好的寫法。
setTimeout 跟setInterval 兩種方式,雖大致上可以達成目的,但會遇到兩個問題1. 程式碼很醜,一層包一層 2. interval 是併發性不是依序性。比較需要解釋的是第二點,當interval 設定的時間小於程式執行時間,容易發生重複執行指令的問題。因此希望可以有依序性的程式寫法,進而找到 async。而在進入async 設計前,會先講到callback跟Promise。
Callback
callback 指的是將function 當作參數傳入,當主方法執行完畢後,就執行傳入的function。
setTimeout 跟setInterval 也是使用到callback 的例子。
function myDisplayer(some) {
document.getElementById("demo").innerHTML = some;
}
function myCalculator(num1, num2, myCallback) {
let sum = num1 + num2;
myCallback(sum);
}
// 執行完 myCalculator的sum 之後,就呼叫myDisplayer 操作element
// myCallback 是參數,所以可以置換成除了myDisplayer 的其他function
myCalculator(5, 5, myDisplayer);
Promise
Promise 使用then/catch 串接,每個then/catch 都是一個promise,來確保每一個步驟是依序執行的。
promise 有兩個狀態: resolve, reject。當promise/then/catch 裡面的邏輯正常執行,會將回傳值放入resolve,進到下一個then;反之,當出現錯誤,就會將回傳值放到reject,進到下一個catch。
有以下幾種用法
let promise = doSomething();
// 標準
promise.then(successCallback, failureCallback);
// 省略failureCallback
promise.then(successCallback);
// 省略successCallback
promise.catch(failureCallback);
範例如下
new Promise((resolve, reject) => {
console.log("Initial");
resolve();
})
.then(() => { // then1
throw new Error("Something failed");
console.log("Do this");
})
.catch(() => {
console.log("Do that");
})
.then(() => { // then2
console.log("Do this whatever happened before");
});
以上會輸出
Initial
// 因為第一個then 發生例外狀況,所以走catch > then2
Do that
Do this whatever happened before
對於同時有多個Promise 執行時有靜態方法可以應用:
- Promise.all(): 只有全部的 promise 成功,才會回傳成功,任一個失敗就會直接失敗
- Promise.allSettled(): 等待全部的 promise 執行結束,不論成功還是失敗都回傳
- Promise.any(): 回傳先「成功」的 promise
- Promise.race(): 回傳先「結束」的 promise
async/await
async/await 是語法糖,方便撰寫Promise。
async 部分如下
async function myFunction() {
return 'Hello';
}
// 等同於
new Promise((resolve, reject) => {
resolve('Hello')
})
await 部分如下
要注意await 只能在async 方法裡面使用
let promise = new Promise((resolve, reject) => {
resolve('Hello')
})
function showMessage(message){
console.log(message)
}
promise.then(result => showMessage(result))
// 等同於
async function main(){
let result = await promise
showMessage(result)
}
main()
另外在await 在處理Promise 的錯誤時,可以用try-catch
let promise = new Promise((resolve, reject) => {
reject('Hello')
})
function showMessage(message){
console.log(message)
}
async function main(){
try{
let result = await promise
showMessage(result)
}
catch(e){
showMessage('error: ' + e)
}
}
main()
> error: Hello
心得
- then 的好處是可以不被限制在async 方法內執行,但缺點是很多then 接在一起可讀性會降低
- await 可以提升可讀性,缺點是要在async 裡面才能執行
- 要注意呼叫async 方法後,後面若有同步的方法會直接執行。所以會造成坊間所說的: 非同步程式不好偵錯
Refences:
https://www.w3schools.com/js/js_callback.asp
https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide/Using_promises
https://medium.com/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/javascript-promise-%E7%94%A8%E6%B3%95%E8%88%87%E5%B8%B8%E8%A6%8B%E7%9A%84%E7%B7%B4%E7%BF%92%E9%A1%8C%E5%9E%8B-80cdbb7753b7