Udemy課程:[The Web Developer Bootcamp 2021(Colt Steele)](https://www.udemy.com/course/the-web-developer-bootcamp/) # 第 27 節: Async JavaScript: Oh Boy! ###### tags: `JavaScript` `Udemy` `The Web Developer Bootcamp 2021` 2021.04.12(Mon.) 2021.08.11(Wed.)~2021.09.14(Tue.) ## ● 上課筆記 ## 1. Call Stack(呼叫堆疊) > 參考網站:[一次了解 JS 中的 Event loop、Call Stack 與 Task Queue](https://snh90100.medium.com/%E4%B8%80%E6%AC%A1%E4%BA%86%E8%A7%A3-js-%E4%B8%AD%E7%9A%84-event-loop-call-stack-%E8%88%87-task-queue-c8041dd8840f) > 參考影片:[所以說event loop到底是什麼玩意兒?| Philip Roberts | JSConf EU](https://www.youtube.com/watch?v=8aGhZQkoFbQ) > 參考網站:[可用來觀察程式碼的call stack步驟](http://latentflip.com/loupe/?code=ZnVuY3Rpb24gbXVsdGlwbHkoeCx5KSB7CiAgICByZXR1cm4geCAqIHk7Cn0KCmZ1bmN0aW9uIHNxdWFyZSh4KSB7CiAgICByZXR1cm4gbXVsdGlwbHkoeCx4KTsKfQoKZnVuY3Rpb24gaXNSaWdodFRyaWFuZ2xlKGEsYixjKXsKICAgIHJldHVybiBzcXVhcmUoYSkgKyBzcXVhcmUoYikgPT09IHNxdWFyZShjKTsKfQoKaXNSaWdodFRyaWFuZ2xlKDMsNCw1KQ%3D%3D!!!)(也可以利用google devtools) JavaScript是單執行緒,也就是說他一次只能執行一個任務,而在等待執行的任務會被放入一個stack(堆疊)。 ```javascript= const multiply = (x,y) => x * y const square = (x) => multiply(x,x) const isRightTriangle = (a,b,c) => { square(a) + square(b) === square(c) } isRightTriangle(3,4,5) ``` 以上述程式碼為例,他就會先讀到第9行,再到第5行,再到第4行,再到第1行,等第1行代回第3行,得到的值再代回第5行,然後繼續循環,直到a、b、c三者都有值。 ## 2. Single Threaded(單執行緒) > 參考網站:[單執行緒&非同步](https://ithelp.ithome.com.tw/articles/10200054) JavaScript是單執行緒,也就是說他一次只能執行一個任務,而在等待執行的任務會被放入一個==stack(堆疊)==。 不過如果遇到一些耗時、不確定觸發執行時間的操作(例如:`setTimeout`、`event callback`、`Http request`、`ajax`等等)就會以非同步處理,也就是會先被丟到事件==Queue(佇列)==,等到同步執行的程式碼執行完,才會去處理那些被放到佇列中的任務。 簡單來說,會依序把程式放進stack裡面,執行完後才會拉queue裡的東西出來執行,直到queue裡面沒東西了,這個查看的過程就稱為 Event Loop。 至於stack跟queue的差異在哪裡?就用圖片來說明: ![](https://i.imgur.com/EJtqyxD.png) * **stack:** 就如他的英文,他是堆疊的,因此先進去的,就會最後出來(先進後出)。 可以使用push( )與pop( )來實現。 * **queue:** 中文是佇列的意思,可以想像是排隊的感覺,先進去就會先出來(先進先出)。 可以使用push( )與shift( )來實現。 上面提到的`setTimeout`、`event callback`、`Http request`、`ajax`這些,其實就是web api function的一種,他不會讓JavaScript去執行,那些耗時、不確定觸發執行時間的操作則是先丟給browser(可能是C++)去執行,等待時間到或者觸發執行後,才會再透過JaavaScript去執行function中的程式碼。 ## 3. Callback Hell(回調地獄) > 參考網址:[callback hell 與 Promise ,一起來把 setTimeout 封裝成 Promise 吧!](https://realdennis.medium.com/callback-hell-%E8%88%87-promise-%E4%B8%80%E8%B5%B7%E4%BE%86%E6%8A%8A-settimeout-%E5%B0%81%E8%A3%9D%E6%88%90-promise-%E5%90%A7-e542ef84967f) > 參考網址:[你懂 JavaScript 嗎?#23 Callback](https://ithelp.ithome.com.tw/articles/10206555) > 參考網址:[面試官:你知道Callback Hell(回調地獄)嗎?](https://kknews.cc/zh-tw/news/r55gmrx.html) > 參考網址:[Callback Hell](http://callbackhell.com/) > 參考網址:[重新認識 JavaScript: Day 18 Callback Function 與 IIFE](https://ithelp.ithome.com.tw/articles/10192739) ==推薦== * ### **什麼是Callback function?** <font color="red">把函式當作另一個函式的參數,透過另一個函式來呼叫它。而這個被作為參數帶入的函式將在「未來某個時間點」被呼叫和執行。這是處理非同步事件的一種方式。</font> 例如說要進入霍格華茲的巫師「入學時要先戴上分類帽」,「戴上分類帽後就會分學院」,如果拿程式碼來說明就會像下面那樣: ```javascript= wizard.addEventListener("入學" ,function(){ 戴上分類帽() wizard.addEventListener("戴上分類帽後" ,function(){ 分學院() }) }) ``` `戴上分類帽()`這個函式只會在滿足了`入學`這個條件才會被動地去執行,我們就可以說這是一個 Callback function。 常見例子:`setTimeout()`、`setInterval()`、DOM 的事件監聽、從資料庫或遠端伺服器請求資料 而當callback超多層時,就會像下面這樣,也就是所謂的「**callback hell**」: ![](https://i.imgur.com/z3ftBVq.gif) ![](https://i.imgur.com/DrIxMYw.jpg) ## 4. Demo: fakeRequest Using What? > 參考網址:[[筆記] 認識同步與非同步 — Callback + Promise + Async/Await](https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/%E5%BF%83%E5%BE%97-%E8%AA%8D%E8%AD%98%E5%90%8C%E6%AD%A5%E8%88%87%E9%9D%9E%E5%90%8C%E6%AD%A5-callback-promise-async-await-640ea491ea64) 接著重點是,非同步事件的處理方法有三種:**callback**、**promise**、**Async/Await 語法** :::warning * ### Demo: fakeRequest Using Callbacks 示範: //利用假請求來示範 ```javascript= const fakeRequestCallback = (url, success, failure) => { const delay = Math.floor(Math.random() * 4500) + 500; setTimeout(() => { if (delay > 4000) { failure('Connection Timeout :(') } else { success(`Here is your fake data from ${url}`) } }, delay) } ``` //非同步事件Callbacks解決方法 ```javascript= fakeRequestCallback('books.com/page1', function (response) { console.log("IT WORKED!!!!") console.log(response) fakeRequestCallback('books.com/page2', function (response) { console.log("IT WORKED AGAIN!!!!") console.log(response) fakeRequestCallback('books.com/page3', function (response) { console.log("IT WORKED AGAIN (3rd req)!!!!") console.log(response) }, function (err) { console.log("ERROR (3rd req)!!!", err) }) }, function (err) { console.log("ERROR (2nd req)!!!", err) }) }, function (err) { console.log("ERROR!!!", err) }) ``` ::: :::warning * ### Demo: fakeRequest Using Promise 示範: //利用假請求來示範(建立 Promise 的事件) ```javascript= const fakeRequestPromise = (url) => { return new Promise((resolve, reject) => { const delay = Math.floor(Math.random() * (4500)) + 500; setTimeout(() => { if (delay > 4000) { reject('Connection Timeout :(') } else { resolve(`Here is your fake data from ${url}`) } }, delay) }) } ``` //非同步事件Promise解決方法 ```javascript= fakeRequestPromise('yelp.com/api/coffee/page1') .then(() => { console.log("IT WORKED!!!!!! (page1)") fakeRequestPromise('yelp.com/api/coffee/page2') .then(() => { console.log("IT WORKED!!!!!! (page2)") fakeRequestPromise('yelp.com/api/coffee/page3') .then(() => { console.log("IT WORKED!!!!!! (page3)") }) .catch(() => { console.log("OH NO, ERROR!!! (page3)") }) }) .catch(() => { console.log("OH NO, ERROR!!! (page2)") }) }) .catch(() => { console.log("OH NO, ERROR!!! (page1)") }) ``` // THE CLEANEST OPTION WITH THEN/CATCH // RETURN A PROMISE FROM .THEN() CALLBACK SO WE CAN CHAIN! ```javascript= fakeRequestPromise('yelp.com/api/coffee/page1') .then((data) => { console.log("IT WORKED!!!!!! (page1)") console.log(data) return fakeRequestPromise('yelp.com/api/coffee/page2') }) .then((data) => { console.log("IT WORKED!!!!!! (page2)") console.log(data) return fakeRequestPromise('yelp.com/api/coffee/page3') }) .then((data) => { console.log("IT WORKED!!!!!! (page3)") console.log(data) }) .catch((err) => { console.log("OH NO, A REQUEST FAILED!!!") console.log(err) }) ``` ::: ## 5. promise > 參考網址:[JavaScript Promise 全介紹](https://wcc723.github.io/development/2020/02/16/all-new-promise/) ==推薦== > 參考網址:[鐵人賽:使用 Promise 處理非同步](https://wcc723.github.io/javascript/2017/12/29/javascript-proimse/) 上面看過例子後,這裡再整理一些有關promise的重點。 * ### Promise 物件狀態: (a)pending:初始狀態,不是 fulfilled 或 rejected。 (b)resolve:表示操作成功地完成 (c)reject:表示操作失敗 ![](https://i.imgur.com/ozbmM7i.png) 以上面例子來說,建立了 Promise 的事件,等待需要調用的時候呼叫它,這類型的事件可能失敗、可能成功,因此會透過 resolve 及 reject 來帶入成功與否的訊息;與此相對應的會用 then() 及 catch()來接收。 then()接成功訊息、catch()接失敗訊息。 也就是說,當promise物件的狀態為resolve時,then()後方function會執行;當promise物件的狀態為reject時,catch()後方function會執行。 * ### RETURN A PROMISE FROM .THEN() CALLBACK SO WE CAN CHAIN! 要進行確保 Promise 任務結束後在進行下一個任務時,就可以使用 return 的方式進入下一個 then。這樣就可以避免巢狀。 ## 6.將callback改寫為promise用法 1. 假的請求 :::warning * ### callback示範: ```javascript= const delayedColorChange = (newColor, delay, doNext) => { setTimeout(() => { document.body.style.backgroundColor = newColor; doNext && doNext(); }, delay) } ``` ::: :::info * ### promise改寫示範: ```javascript= const delayedColorChange = (color, delay) => { return new Promise((resolve, reject) => { setTimeout(() => { document.body.style.backgroundColor = color; resolve(); }, delay) }) } ``` ::: 2. 非同步事件解決方法 :::warning * ### callback示範: ```javascript= delayedColorChange('red', 1000, () => { delayedColorChange('orange', 1000, () => { delayedColorChange('yellow', 1000, () => { delayedColorChange('green', 1000, () => { delayedColorChange('blue', 1000, () => { delayedColorChange('indigo', 1000, () => { delayedColorChange('violet', 1000, () => { }) }) }) }) }) }) }); ``` ::: :::info * ### promise改寫示範: ```javascript= delayedColorChange('red', 1000) .then(() => delayedColorChange('orange', 1000)) .then(() => delayedColorChange('yellow', 1000)) .then(() => delayedColorChange('green', 1000)) .then(() => delayedColorChange('blue', 1000)) .then(() => delayedColorChange('indigo', 1000)) .then(() => delayedColorChange('violet', 1000)) ``` ::: ## 7. Async keyword > 參考網址:[Async function / Await 深度介紹](https://wcc723.github.io/development/2020/10/16/async-await/) > 參考網址:[JavaScript 錯誤 - Throw 和 Try to Catch](https://www.w3school.com.cn/js/js_errors.asp) async function 所宣告的函式,可在其內以「同步的方式運行非同步」,與一般函式差別在於,一般函式在console.log中呼叫時,會回傳函式內部的內容;async函式在console.log查看時,則是得到與 Promise 結構相似的函式,是無法直接使用 console.log 取值的。 ```javascript= function hello(){ return"abc" } //在console.log中呈現"abc" async function hello(){ return"abc" } //在console.log中呈現Promise{<resolve>:"abc"} //另一種async寫法: const hello = async () =>{ return"abc" } ``` * JavaScript 錯誤 - Throw 和 Try to Catch try語句使我們能夠測試代碼塊中的錯誤。 catch語句允許我們處理錯誤。 throw語句允許我們創建自定義錯誤。 * 示範: ```javascript= const login = async (username, password) => { if (!username || !password) throw 'Missing Credentials' if (password === 'corgifeetarecute') return 'WELCOME!' throw 'Invalid Password' } login('todd', 'corgifeetarecute') .then(msg => { console.log("LOGGED IN!") console.log(msg) }) .catch(err => { console.log("ERROR!") console.log(err) }) ``` ## 8. Await keyword > 參考網站:[Async function / Await 深度介紹](https://wcc723.github.io/development/2020/10/16/async-await/) await 是屬於一元運算子,但是在原始碼中直接運行 await 則會出現錯誤,它只能在 async function 中運行,所以 async/await 基本上是一體的,不會單獨出現。 await會直接回傳後方表達式的值;但如果是 Promise 時則會 “等待” resovle 的結果並回傳。 再從第6點的promise寫法做改寫: :::success * ### async/await : ```javascript= async function rainbow() { await delayedColorChange('red', 1000) await delayedColorChange('orange', 1000) await delayedColorChange('yellow', 1000) await delayedColorChange('green', 1000) await delayedColorChange('blue', 1000) await delayedColorChange('indigo', 1000) await delayedColorChange('violet', 1000) return "ALL DONE!" } ``` ::: 網頁畫面會依序顯示紅橙黃......紫色跑出,也就是說使用await,他必須等上面的城市執行完畢,才會繼續執行下去。