JavaScript 筆記:非同步與 AJAX Fetch
最常見的 Asynchronous JavaScript 用法,就是用 JavaScript 從後端拿資料。因為拿資料會花時間,如果還沒拿到資料就繼續執行後面的程式(例如處理資料的程式碼),就可能出錯。所以我們需要用 Asynchronous JavaScript 來避免這種問題。
同步 (Synchronous) 與非同步 (Asynchronous)
同步(Synchronous)就是 JavaScript 具有 One Single Thread,所以程式會一行一行照順序執行。像下面的程式,當我們呼叫 first(),會先印出 'execute 1',再印出 'execute 2',最後才是 'The end'。每一行都要等前一行做完才會繼續,這就是同步的 JavaScript。
Synchronous 範例
這樣的流程稱為同步 (Synchronous),在任務完成前,下一行程式不會被執行。

Asynchronous 範例
了解同步的運作後,接下來我們用一個簡單例子來說明什麼是非同步。下面的程式用 setTimeout() 來假裝我們在等外部 API 回傳資料(等 2 秒才拿到資料)。我們會看到 execute1-3 會先被印出來,等兩秒後才會執行 setTimeout() 裡的回呼函式。
這裡用 JavaScript 的執行流程來說明。從下方 GIF 可以看到,當 console.log("execute 2") 執行完後,setTimeout 會被加入執行堆疊,但它裡面的回呼函式(callback)會被丟到瀏覽器的 Web APIs,等 2 秒後才會回來。這時,主程式不用等 2 秒,可以直接繼續執行後面的 console.log('The end')。
事件循環 (event loop)
等 2 秒倒數結束後,setTimeout 的回呼函式會被放到 Callback Queue(回呼佇列)裡排隊。這時,事件循環(event loop)就會檢查,如果執行堆疊已經清空(只剩下全域環境),就會把這個回呼函式拉進來執行。所以,setTimeout 的回呼函式其實不是在 first() 裡面馬上執行,而是等其他程式都跑完、主線程空了,才會在背景執行。

關於 非同步的 Callback Hell
下面的程式碼可以看到,如果要做的事情變多,就會一直重複用 setTimeout(),而且每個回呼函式裡又包著另一個回呼函式,層層包起來,讓程式碼變得很亂、很難維護。這種情況就叫做「回呼地獄(Callback Hell)」。後來 ES6 推出了新寫法,才讓這種問題變得好解決,程式碼也更好讀。
Promise (解決 Callback Hell)
前面提到「回呼地獄 (callback hell)」會讓程式碼很難讀、很難維護。為了讓非同步程式碼更簡單、更有條理,我們可以用 ES6 的 new Promise() 來寫。
下面的範例用 Promise() 建立三個功能(getIDs、getRecipes、getRelated)。每個 Promise 裡面,如果執行成功,就用 resolve() 把結果傳出去;如果有錯誤,就用 reject() 處理。
Async & Await
Promise 雖然讓非同步程式碼比較好讀,但 ES8 (ES2017) 又推出了更簡單的寫法:Async Await。只要在函式前加上 async,裡面遇到 Promise 時在前面加上 await,就能像寫同步程式一樣,讓程式一行一行執行。await 會等到 Promise 有結果(resolve)後,才把資料存進變數(像是 IDs、recipe1、recipe2)。
NOTE:
await只能在async函式中使用。若要取得回傳值,可搭配.then():