【JavaScript】非同步 async

平常使用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

心得

  1. then 的好處是可以不被限制在async 方法內執行,但缺點是很多then 接在一起可讀性會降低
  2. await 可以提升可讀性,缺點是要在async 裡面才能執行
  3. 要注意呼叫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