回呼地獄 (Callback Hell)
讓我們使用 AJAX 來模擬通過一系列關卡的過程,並展示回呼地獄(Callback hell)的情況。在這個例子中,我們將使用 jQuery 的 AJAX 函式來發送非同步請求,成功後呼叫回呼函式。
假設每個關卡需要獲取一些資源,我們將透過 AJAX 來模擬這個過程。以下是範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| function passStage1(callback) { console.log("通過關卡 1..."); $.ajax({ url: "/passStage1", method: "GET", success: function() { console.log("關卡 1 完成!"); callback(); }, error: function() { console.error("通過關卡 1 時發生錯誤!"); } }); }
function passStage2(callback) { console.log("通過關卡 2..."); $.ajax({ url: "/passStage2", method: "GET", success: function() { console.log("關卡 2 完成!"); callback(); }, error: function() { console.error("通過關卡 2 時發生錯誤!"); } }); }
function passStage3(callback) { console.log("通過關卡 3..."); $.ajax({ url: "/passStage3", method: "GET", success: function() { console.log("關卡 3 完成!"); callback(); }, error: function() { console.error("通過關卡 3 時發生錯誤!"); } }); }
passStage1(function() { passStage2(function() { passStage3(function() { console.log("恭喜你!你通過了所有關卡並贏得了遊戲!"); }); }); });
|
在這個例子中,我們定義了三個函式 passStage1
、passStage2
和 passStage3
,每個函式代表一個關卡。當玩家通過一個關卡後,將執行下一個關卡的函式。使用 AJAX 發送請求,當請求成功後,執行回呼函式,通過巢狀的回呼函式來模擬玩家通過一系列關卡的過程。
這樣看起來「不夠地獄」,實務上可能寫成這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| console.log("通過關卡 1..."); $.ajax({ url: "/passStage1", method: "GET", success: function() { console.log("關卡 1 完成!"); console.log("通過關卡 2..."); $.ajax({ url: "/passStage2", method: "GET", success: function() { console.log("關卡 2 完成!"); console.log("通過關卡 3..."); $.ajax({ url: "/passStage3", method: "GET", success: function() { console.log("關卡 3 完成!"); console.log("恭喜你!你通過了所有關卡並贏得了遊戲!"); }, error: function() { console.error("通過關卡 3 時發生錯誤!"); } }); }, error: function() { console.error("通過關卡 2 時發生錯誤!"); } }); }, error: function() { console.error("通過關卡 1 時發生錯誤!"); } });
|
這種巢狀的回呼函式導致了程式碼巢狀層級過深,形成回呼地獄,讓程式碼難以閱讀和維護。這會增加程式碼出錯的可能性,並讓程式碼難以擴展和修改。
當聊到回呼地獄 (Callback Hell) 的時候,很常看到這張圖,可以很貼切的說明上方的情況。
Promise
為了解決這個回呼函式的問題,Promise 誕生了!我們使用現在瀏覽器已經內建的 fetch 來說明 Promise 的使用方式。來看一個實際的例子來說明 fetch
和 Promise
的關係以及如何使用它們。
假設我們想要從一個提供 JSON 資料的 API 獲取一些資訊。這個過程包含發出請求、接收響應並處理這個響應。我們將使用 fetch
函式來發出請求,然後使用 Promise
的方法來處理結果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| fetch('https://jsonplaceholder.typicode.com/users/1') .then(response => { if (!response.ok) { throw new Error('網路請求失敗'); } return response.json(); }) .then(data => { console.log(data); }) .catch(error => { console.error('請求失敗:', error); });
|
在這個例子中:
- 我們使用
fetch
向 https://jsonplaceholder.typicode.com/users/1
發出 GET 請求。
fetch
返回一個 Promise
物件,這個物件最終會被解析成 HTTP 響應。
- 使用
.then()
方法來設定當 Promise
物件解析成功時的回調函式。如果 HTTP 響應表明請求成功(response.ok
為真),我們將響應體轉換為 JSON。response.json()
也返回一個 Promise
物件,因此我們可以在它後面再接一個 .then()
來處理 JSON 資料。
- 第二個
.then()
接收到從 JSON 轉換的資料,並將其輸出到控制台。
- 如果在任何一步驟發生錯誤(例如網路問題或 JSON 解析錯誤),
.catch()
方法將捕獲這些錯誤,並允許我們處理它們。
這個例子展示了 fetch
和 Promise
如何共同工作來處理非同步 HTTP 請求,以及如何使用 .then()
和 .catch()
方法來處理成功和失敗的情況。
fetch vs Axios
接下來我們來進行一個常見的比較 fetch vs Axios。我們分別使用 fetch 和 axios 來發送 GET 請求,並進行比較。假設我們需要從一個 API 獲取使用者列表,以下是使用 fetch 和 axios 的範例程式碼。
使用 fetch
1 2 3 4 5 6 7 8 9 10 11 12 13
| fetch('https://jsonplaceholder.typicode.com/users') .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(data => { console.log('Fetch Users:', data); }) .catch(error => { console.error('Fetch Error:', error); });
|
使用 axios
1 2 3 4 5 6 7
| axios.get('https://jsonplaceholder.typicode.com/users') .then(response => { console.log('Axios Users:', response.data); }) .catch(error => { console.error('Axios Error:', error); });
|
比較
我們進一步比較 fetch 和 axios 在其他方面的差異。
特性 |
fetch |
axios |
支援度 |
fetch 是 Web APIs 的一部分,支援度較好,可以在現代瀏覽器中直接使用。 |
axios 是一個基於 Promise 的 HTTP 客戶端,可以在瀏覽器和 Node.js 中使用,支援度也很好。 |
攔截器 |
不支援攔截器,需要手動處理每個請求和回應。 |
支援攔截器,可以在請求和回應被發送或接收之前進行一些處理,例如添加公共的請求 header、logger 等。 |
取消請求 |
不支援取消請求功能,一旦請求發送就無法取消。 |
支援取消請求功能,可以在需要時取消正在進行的請求,以節省資源和提高性能。 |
自動轉換 JSON 數據 |
需要手動調用. json() 方法將回應轉換為 JSON 格式。 |
自動將回應的 JSON 數據轉換為 JavaScript 物件,無需手動處理。 |
客製化配置 |
不支援設置全局的默認配置,每次請求需要手動設置相關選項。 |
支援設置全局的默認配置,例如設置基本的 URL、超時時間、請求頭等,方便統一管理和使用。 |
axios 提供了許多 fetch 所不具備的便利功能,例如攔截器、取消請求、自動轉換 JSON 數據等,這使得 axios 在實際應用中更加靈活和方便。