---
# System prepended metadata

title: 第二堂：Clean Code 非同步程式設計的清晰之道
tags: ["前端家教師資培訓班\_- 2024"]

---

# 第二堂：Clean Code 非同步程式設計的清晰之道

## 開課提醒
1. 錄影
2. 通知：洧杰釋出第三堂試教錄影


## 今日上課知識點
1. 非同步 介紹
2. Clean Code - 非同步、錯誤處理

---

## 非同步：從 Callback Function 到 async/await

### 同步與非同步的差異 
**同步 → 同一個步道**
<img src="https://hackmd.io/_uploads/SyM8j8oXa.png" width="350">
```jsx
console.log('1, 我先執行');
console.log('2, 等 1 執行完才換我');
console.log('3, 等 2 執行完才換我');
```

→ 阻塞後續程式碼

**非同步 → 不同步道**
<img src="https://hackmd.io/_uploads/SkGKsLsma.png" width="350">

```jsx
console.log('1, 我先執行');

setTimeout(() => {
  console.log('3, 非同步操作完成');
}, 2000);
    
console.log('2, 我會第二個出現！')
```
→ 不會阻塞後續程式碼
→ 會有無法預期誰先執行、誰先完成的問題

### Callback Function
早期用 Callback Function 來處理非同步事件，來控制操作順序
```jsx
buttonElement.addEventListener("click", {
  handleEvent: function (event) {
    alert("Element clicked through handleEvent property!");
  },
});
```
可以用 callback function 的形式去確認我的非同步操作順序

```jsx
asyncOperation1(() => {
  asyncOperation2(() => {
    asyncOperation3(() => {
      console.log("完成所有非同步操作");
    });
  });
});
```

保證先執行 `asyncOperation1`，再執行 `asyncOperation2`，最後執行 `asyncOperation3`。

.
.
.
.
.
<img src="https://hackmd.io/_uploads/H1gKA-VNR.png" width="350">

多層嵌套的結構導致難理解程式碼、不好閱讀、不好維護，被稱作 Callback Hell
Callback Function 比較適合處理簡單的非同步操作

### Promise
因為 Callback Hell，後來改使用 Promise 來處理非同步事件
Promise 是一個強大的非同步執行流程語法結構，他的工作就是處理多個函式，將他們轉為序列執行（一個接一個）或是並行執行（全部都處理完再說）
```jsx
const promise = new Promise(function(resolve, reject) {
    resolve(1)
});

// 鏈式 chaining
promise.then(function(value){
	return value + 1 // 1
}).then(function(value){
	return value // 2
}).catch(error => {
    // 錯誤處理
});
```
fetch 和 axios 都是在 JavaScript 中用於發送網絡請求(非同步)的工具，兩者都基於 Promise 提供了非同步的請求處理機制。
```javascript
function fetchData() {
    axios.get('https://api.example.com/data')
    .then(response => {
        // 成功執行
        console.log('成功:', response);
    })
    .catch(error => {
        // 失敗執行
        console.error('錯誤:', error);
    });
}

fetchData();
```

### Async Await
ES8 出了 async await 語法，是 Promise 的語法糖，沒有新增的功能，所以一樣可以使用在基於 Promise 的 axios / fetch 語法上。

async await 語法提供了更簡潔的方式來處理非同步，可以讓程式碼提高可讀性(看起來像在寫同步程式碼)。

```jsx
// async 標示該函式為非同步函式
async function fetchData() {
    // await 會等待非同步函式完成再繼續往下執行
    const response = await axios.get('https://api.example.com/data');
    // 等前面完成後，才會執行這一行
    console.log('成功:', response);
}

fetchData();
```

## 非同步、錯誤處理 Clean Code 原則介紹
### 一、 Async/Await 比 Promises 更加簡潔

**糟糕的程式碼範例：**

```jsx
function getUserAndPosts(userId) {
  axios
    .get(`https://hexschool.io/api/users/${userId}`)
    .then((userResponse) => {
      console.log("用戶資料:", userResponse.data);
      return axios.get(`https://hexschool.io/api/users/${userId}/posts`);
    })
    .then((postsResponse) => {
      console.log("用戶貼文:", postsResponse.data);
    });
}

getUserAndPosts(1);
```

**好的程式碼範例：**

將 getUserAndPosts 函式改為了 async，並且使用 await 關鍵字來等待 axios.get 的執行結果。這樣寫解決了多層嵌套的結構，轉為線性寫法更簡潔、易讀，也更容易理解程式碼的流程。



```jsx
async function getUserAndPosts(userId) {
    const userResponse = await axios.get(`https://hexschool.io/api/users/${userId}`);
    console.log('用戶資料:', userResponse.data);

    const postsResponse = await axios.get(`https://hexschool.io/api/users/${userId}/posts`);
    console.log('用戶貼文:', postsResponse.data);
}

getUserAndPosts(1);
```
⭐️小練習：以此原則，請調整以下程式碼成 async await 寫法

```jsx
const removeCart = (cartId) => {
    axios.delete(`https://hexschool.io/api/carts/${cartId}`)
    .then(response => {
        console.log(response);
    })
}

removeCart(1)
```

### 二、例外處理
用來處理程式執行過程中出現的非預期錯誤

**補充：錯誤捕捉**

有些非預期錯誤可能會讓程式直接停止，代表網頁的操作會中斷。透過例外處理，程式可以在錯誤發生後，執行特定的邏輯來捕捉錯誤。

常見的例外處理方式：
1. try…catch
try catch 會先將 try 區塊中的程式碼執行一次，
如果裡面的程式碼有問題就會立刻中止執行，執行步驟會直接跳到 catch 區塊的程式碼
如果裡面的程式碼沒有問題，就會忽略 catch 區塊的程式碼
    ```jsx
    try {
     // 要做的事情
    } catch(error) {
     // 例外處理
    }
    ```
2. .catch() 
在使用 Promise 時，catch 語句可以幫我們「捕獲（catch）」錯誤
    ```jsx
    axios.get('https://api.example.com/data')
      .then(response => {
         console.log(response.data); 
    }).catch(error => {
         console.log("捕捉到錯誤:", error.message);
    });

    // 改用 async await 寫法
    async function fetchData() {
        try {
            const response = await axios.get('https://api.example.com/data');
            console.log(response.data);
        } catch (error) {
            console.log("捕捉到錯誤:", error.message); 
        }
    }
    fetchData();
    ```

⭐️小練習：以此原則，請調整以下程式碼為 async await 寫法，並將錯誤使用 try catch 做捕捉
```jsx
function getUserAndPosts(userId) {
    axios.get(`https://hexschool.io/api/users/${userId}`)
        .then(userResponse => {
            console.log('用戶資料:', userResponse.data);
            return axios.get(`https://hexschool.io/api/users/${userId}/posts`);
        })
        .catch(error => {
            console.log('獲取用戶資料錯誤:', error);
        })
        .then(postsResponse => {
            console.log('用戶貼文:', postsResponse.data);
        })
        .catch(error => {
            console.log('獲取貼文錯誤:', error);
        });
}

getUserAndPosts(1);
```







### 三、不要忽略捕捉到的錯誤
**糟糕的程式碼範例：**

僅僅是將錯誤印到 console，但沒有進一步的處理。

```jsx
async function addUser() {
  try {
    const response = await axios.post(`https://hexschool.io/api/users/`, {
        name: 'Alice',
        role: 'admin'
    });
    console.log(response);
  } catch (err) {
    console.log(err);
  }
}

addUser()
```

**好的程式碼範例：**

除了印錯誤到控制台外，還使用 `notifyUserOfError` 函式通知用戶錯誤，以及`reportErrorToService`函式將錯誤報告給服務端。這樣的處理方式有助於管理錯誤。

```jsx
async function addUser() {
    try {
        const response = await axios.post(`https://hexschool.io/api/users/`, {
	    name: 'Alice',
	    role: 'admin'
    });
        console.log(response);
    } catch (error) {
	  // 比 console.log 更好
	  console.error(error);
	  // 通知用戶錯誤
	  notifyUserOfError(error);
	  // 將錯誤報告給服務端
	  reportErrorToService(error);
	  // 或是全部都做！
	}
}
addUser()
```


**補充：使用 SweetAlert2 做 Modal 提示**
除了把捕捉到的 error 用 console 呈現出來還不夠，我們可以多做額外的處理

[SweetAlert2](https://sweetalert2.github.io/)

```jsx
<script src="
https://cdn.jsdelivr.net/npm/sweetalert2@11.10.8/dist/sweetalert2.all.min.js
"></script>

<link href="
https://cdn.jsdelivr.net/npm/sweetalert2@11.10.8/dist/sweetalert2.min.css
" rel="stylesheet">

Swal.fire("SweetAlert2 is working!");
```
⭐️小練習：以此原則，請調整以下程式碼將捕捉到的錯誤使用 sweetalert2 做呈現

```jsx
const updateUser = async (userId) => {
    try {
        const response = await axios.put(`https://hexschool.io/api/users/${userId}`,   { role: 'user' })
        console.log(response)
    } catch (error) {
        console.log(error)
    }
}

updateUser(1)
```
## 作業講解
1. （必做）主線作業：Todo API Clean Code
2. 加碼：[Clean Code 修改練習](https://codepen.io/hexschool/pen/oNRLwVG?editors=1011)
    
    備註：範例中的 API 資料會在每日凌晨 1:15 清除
    
    1. 將提示中的 Clean Code 原則，註解在使用的該行程式碼後面
    2. 至少需要修改 3 個以上
    
    提交格式：
    1. 在 DC 討論串回報 Codepen 連結

3. 報名方案二的同學，可開始在同組尋找一起做「錄製試教影片任務」的夥伴，之後需要錄製三個試教影片，可先觀看洧杰的試教影片

4. 第二週小組任務
