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的差異在哪裡?就用圖片來說明:

* **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**」:


## 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:表示操作失敗

以上面例子來說,建立了 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,他必須等上面的城市執行完畢,才會繼續執行下去。