# 第二堂: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. 第二週小組任務