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