---
# System prepended metadata

title: '第 27 節： Async JavaScript: Oh Boy!'
tags: [The Web Developer Bootcamp 2021, JavaScript, Udemy]

---

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，他必須等上面的城市執行完畢，才會繼續執行下去。

