iT邦幫忙

2023 iThome 鐵人賽

DAY 21
0
Vue.js

Vue & GraphQL 探險之旅:30天,從新手村到魔王之巔系列 第 21

[Day21] 錯誤處理:Vue 和 GraphQL 中 Error handling 的機制與策略

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20231015/20141111DQwedShSFK.png

錯誤處理是軟體開發中的關鍵部分。

隨著現代前端框架的迅速發展,如何在 Vue 中策略性地、有效地處理各種錯誤—無論是預期還是非預期的—已成為每位開發者的核心技能。

此外,GraphQL 強調 Schema 和 Type 的嚴格性,對於錯誤的定義和格式也有明確的規範,確保前後端在錯誤處理上能夠達到高度的一致性與穩定性。

在這篇文章中,我們將深入探討 Vue 和 GraphQL 中的錯誤處理策略以及實踐基礎,一同打造更穩固、更韌性的 Vue 應用。

https://ithelp.ithome.com.tw/upload/images/20231027/20141111GbytpNTjm5.jpg
(謎之音:錯誤處理做得好,解釋 Bug 沒煩惱~)


目錄

  • 錯誤處理的重要性
    • 預期與非預期錯誤
    • 使用者體驗與應用穩定性
  • Vue 的錯誤捕捉策略
    • Lifecycle Hook - errorCaptured
    • 全局錯誤處理
    • Vue 的錯誤傳遞機制
  • GraphQL 的錯誤處理機制設計
    • 200 is not OK
    • 基本的錯誤格式
    • 報告策略
    • 客製化錯誤
    • 常見的誤區

錯誤處理的重要性

還記得大學時期熬夜趕報告的日子嗎?可能努力地敲了好幾個小時,結果...

「糟糕!」
https://ithelp.ithome.com.tw/upload/images/20231027/20141111OMQoWUlAZF.png

Word 突然當機,所有的努力似乎都白費了。
 

但後來,微軟加入了錯誤恢復功能,當 Word 再次開啟時,它會提示您恢復未保存的文件。

這不僅讓使用者鬆了一口氣,更是一個鮮明的例子:錯誤處理真的很重要

畢竟,誰都不想再有「哎呀,我剛才有存檔嗎?」這種心跳加速的瞬間。
https://ithelp.ithome.com.tw/upload/images/20231027/20141111u9rgDQA0UA.jpg


預期與非預期錯誤

錯誤的分類與管理是一個關鍵的概念。
尤其越大型、複雜的專案,越需要建立一套合適的錯誤處理架構。

但從基本角度來看,應用程式的錯誤主要可以劃分為兩大類:預期錯誤與非預期錯誤。

可預期的錯誤

那些可以被開發者預測到的錯誤,被歸類為「可預期的錯誤」。

例如:使用者輸入了無效的電子郵件地址或密碼、輸入的付款卡號不正確,或選擇了一個已被預訂的日期。

https://ithelp.ithome.com.tw/upload/images/20231027/20141111bLp8saavpD.jpg

對於這些錯誤,我們可以給予使用者明確且具指導性的回饋,幫助他們進行正確操作。

非預期的錯誤

非預期的錯誤」往往源於未知的因素或外部依賴的問題,例如伺服器突然中斷服務。

面對這類錯誤,我們的首要任務是確保它們不會大幅地影響使用者的體驗,並給予使用者一個基本的通知,讓他們知道目前存在某些問題。


使用者體驗與應用穩定性

使用者體驗,是指使用者在使用產品或服務時的整體感受和互動體驗。
應用穩定性,則是描述軟體在執行時的可靠性和持續性,沒有出現非預期的問題或崩潰。

一個好的應用,不僅要功能齊全、介面漂亮,最重要的是操作便利順暢,讓使用者不會因為某些錯誤而感到困擾。
畢竟,若失去使用者的信賴,他們可能再也不回來了。

使用者體驗涉及專門的 UI/UX 設計知識,而確保應用穩定性,就要依賴工程師的細心與熟練技巧。


Vue 的錯誤捕捉策略

任何語言的錯誤處理,基本上都從 try & catch 開始。

作為 JavaScript 的框架 — Vue 當然也不例外。
更進一步,我們還可以利用 Vue 的 生命週期鉤子 (Lifecycle Hook)事件監聽器 (Event Listener) 來捕捉和處理錯誤。

Lifecycle Hook - errorCaptured

Vue 提供了 errorCaptured 鉤子 (hook),它就像 React 中的錯誤邊界 (Error Boundaries)。

當 Component 捕捉到 Child Component 報出的錯誤時,這個鉤子會被觸發。
開發者可以在這裡進行錯誤的日誌記錄、報告、提供某些備用 UI,或是向上傳遞錯誤,使其可以繼續被上層 Component 的 errorCaptured 或全局錯誤處理捕捉到。

錯誤捕捉的來源有:

  • Component renders
  • Event handlers
  • Lifecycle hooks
  • setup() function
  • Watchers
  • Custom directive hooks
  • Transition hooks

參考文件
Options API - errorCaptured
Composition API - onErrorCaptured()

全局錯誤處理

透過設定 Vue.config.errorHandler,開發扯可以定義全局的錯誤處理函數。

當 Vue 的渲染函數、生命週期鉤子或其他部分出現未被捕獲的錯誤時,這個函數會被呼叫。這意味著,如果錯誤沒有在 errorCaptured 中被處理或向上傳遞,它最終會在這裡被捕捉到。

範例:

import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);

// 設置全局的錯誤處理器
app.config.errorHandler = (err, vm, info) => {
    // 在此處理錯誤
    // `err` 是錯誤物件
    // `vm` 是出錯的 Vue instance
    // `info` 是 Vue 特定的錯誤資訊,例如錯誤所在的生命週期鉤子等
    console.error(`Error occurred: ${err.toString()}`);
    console.error(`Error source instance:`, vm);
    console.error(`Vue-specific error info: ${info}`);

    // 可以將錯誤信息發送到後端日誌系統或第三方錯誤追踪工具
    // sendErrorToServer(err, vm, info);
};

app.mount('#app');

參考文件
app.config.errorHandler

Vue 的錯誤傳遞機制

Vue 提供了 Component 層級的錯誤處理,也有全局的錯誤處理,接著讓我們來看看當錯誤發生時,Vue 的傳遞與處理流程:

  1. 預設情況下,所有錯誤都會被發送到 app.config.errorHandler(當然,這是基於開發者已經設定了這個功能的前提下)。
  2. 若組件鏈中有好幾個 errorCaptured,系統會從最底層的子組件往上逐一觸發。
  3. 若組件中的 errorCaptured 內出錯了,那麼它抓到的錯誤和自己製造的錯誤,兩個都會送到 app.config.errorHandler
  4. 若要阻止錯誤繼續向上傳遞,只需要在 errorCaptured 回傳 false

了解基本的錯誤處理機制後,我們能夠設計更多元的錯誤處理策略,進而提升使用者體驗和應用的穩定性。

緊接著,讓我們來探討另一主角 - GraphQL 的錯誤處理機制。


GraphQL 的錯誤處理機制設計

GraphQL 設計的初衷是讓前後端的資料需求和溝通更加靈活和精確。

當我們使用 GraphQL 進行資料請求時,有時候可能會碰到某些錯誤。
為了讓前端能夠更好地理解和處理這些錯誤,GraphQL 規範制定了一個明確的錯誤回應格式。

200 is not OK

GraphQL 跟 RESTful 一個很明顯的差異,就是 HTTP Status Code 200 不等於正常

GraphQL 的規範故意沒有提到 HTTP,因為 GraphQL 是不依賴於傳輸協議的。規範的實現者可以選擇實施規範的傳輸機制。

因此,GraphQL 伺服器在傳遞錯誤時不依賴 HTTP 狀態碼。以下是一個同時包含 dataerrors 的 GraphQL 回應範例:

{
  "errors": [
    {
      "message": "Name for character with ID 1002 could not be fetched.",
      "locations": [{ "line": 6, "column": 7 }],
      "path": ["hero", "heroFriends", 1, "name"]
    }
  ],
  "data": {
    "hero": {
      "name": "R2-D2",
      "heroFriends": [
        {
          "id": "1000",
          "name": "Luke Skywalker"
        },
        null,
        {
          "id": "1003",
          "name": "Leia Organa"
        }
      ]
    }
  }
}

在這個範例中,儘管有錯誤,回應仍然包含了 ID 和名稱欄位,這讓客戶端即使查詢的部分失敗,仍然可以從伺服器獲得有價值的資料。

GraphQL 回應格式規範

GraphQL Spec 的 Response 章節中,詳細規範著 Response 該有的格式。

  1. 基本格式:GraphQL 的回應必須是一個 map。

  2. 錯誤欄位 (errors)

    • 若請求中發生錯誤,回應的 map 中必須包含一個鍵名為 errors 的項目。
    • 若請求成功完成且沒有錯誤,此項目不應出現。
  3. 資料欄位 (data)

    • 若請求包含了執行動作,回應的 map 必須包含一個鍵名為 data 的項目。
    • 若因語法錯誤、缺失資訊或驗證錯誤等原因導致請求在執行前失敗,此項目不應出現。
  4. 擴展欄位 (extensions)

    • 回應的 map 中可以包含一個鍵名為 extensions 的項目。
    • 若設定此項目,它的值必須是一個 map。
    • 此項目實作者可根據自己的需求擴展協議,因此其內容沒有額外的限制。
  5. 確保相容性:為了確保未來的協議改變不會打破現有的服務和客戶端,頂層的回應映射不應該包含上述三者以外的任何項目。

  6. 序列化提示:當回應中包含 errors 時,為了方便調試,建議在序列化時使其排在第一位,以清楚顯示回應中存在的錯誤。

基本的錯誤格式

GraphQL 規範中也進一步制定了 Data 與 Error 項目的限制:

https://ithelp.ithome.com.tw/upload/images/20231029/20141111EOjc3wnlXR.png

https://ithelp.ithome.com.tw/upload/images/20231029/201411117KP028Wew9.png

一個包含錯誤的 GraphQL 回應範例如下:

{
  "errors": [
    {
      "message": "Name for character with ID 1002 could not be fetched.",
      "locations": [{ "line": 6, "column": 7 }],
      "path": ["hero", "heroFriends", 1, "name"],
      "extensions": {
        "code": "CAN_NOT_FETCH_BY_ID",
        "timestamp": "Fri Feb 9 14:33:09 UTC 2018"
      }
    }
  ]
}

讓我們深入探討這個範例中隱含的規範涵義:

Data 與 Error 的基本關係

錯誤列表:當回應中包含 errors 欄位時,表示請求中發生了至少一個錯誤。這個列表內會包含一個或多個錯誤對象。

資料與錯誤的關係

  • 若回應中沒有 data 欄位,那麼 errors 列表必須包含至少一個錯誤,說明為何沒有資料返回。
  • 若回應中有 data 欄位,那麼 errors 可以包含在執行過程中產生的任何錯誤。

錯誤的類型

請求錯誤

  • 這些錯誤發生在執行前,可能是由於請求文件的解析或驗證錯誤、無法確定執行哪個操作、或變數的輸入值無效。
  • 這類錯誤通常是客戶端的錯誤。

欄位錯誤

  • 這些錯誤是在某個特定欄位的執行期間產生的,可能是由於內部錯誤或無法轉換返回的值。
  • 這類錯誤通常是 GraphQL 服務的錯誤。

錯誤結果格式

為了達成一致性的傳遞與處理錯誤,GraphQL 規範提供了一個清晰、統一的方式來描述和傳遞錯誤結果

Error Result Format

  • 每個錯誤都必須包含一個 message 鍵,描述錯誤的詳細信息,幫助開發者理解和修正錯誤。
  • 若錯誤可以與請求中的特定點關聯,應包含 locations 鍵,詳細描述錯誤位置。
  • 若錯誤可以與結果中的特定欄位關聯,則必須包含 path 鍵,詳細描述出錯的欄位路徑。

擴展錯誤:GraphQL 服務可以為錯誤提供一個名為extensions的附加項目。這是為了允許實施者添加他們認為合適的額外錯誤信息。

報告策略

當 GraphQL 伺服器在解析或執行查詢時遇到多個錯誤,它會盡量回傳所有可能的錯誤,而不是在遇到第一個錯誤時就立即停止。這確保了開發者可以一次性看到並修復查詢中的多個問題。

客製化錯誤

雖然 message 是必須的,但開發者可以擴充錯誤物件以包含更多的資訊,例如特定的錯誤碼 (Error Code) 或其他上下文 (Context)。
這樣可以幫助客戶端更精確地處理特定的錯誤情況。


常見的誤區

一個常見的誤區是將所有的問題都用錯誤來表示。

GraphQL 的靈活性讓我們有能力定義清晰、有意義的回應,而不是簡單地將所有不符合預期的情況都歸為「錯誤」。

在真實的應用場景中,不同的狀況需要不同的回饋方式,而不是僅僅通過一個錯誤代碼或訊息。

「問題」不一定是系統的「錯誤」

以常見的 Not Found 情境作為案例:

案例一:查詢某城市的天氣,但該城市不在資料庫中。
案例二:查詢某城市的未來一週天氣,但系統目前只能提供未來三天的資料。

如果我們將所有案例都使用「錯誤」來描述,可能會讓用戶誤解為是系統出了問題,但實際上這只是一個正常的業務流程。

更好的做法,是提供使用者 更溫和、具體且有幫助的回饋
不僅可以給使用者一個更好的用戶體驗,還可以指引他們進行下一步的操作。

案例一:查詢某城市的天氣,但該城市不在資料庫中。

不良的錯誤回饋

{
  "errors": [
    {
      "message": "系統錯誤:找不到城市。"
    }
  ],
  "data": null
}

更適當的回饋

{
  "data": {
    "weather": null
  },
  "extensions": {
    "userMessage": "很抱歉,我們的資料庫中沒有該城市的天氣資訊,請確認城市名稱是否正確或試著查詢其他城市。"
  }
}

案例二:查詢某城市的未來一週天氣,但系統目前只能提供未來三天的資料。

不良的錯誤回饋

{
  "errors": [
    {
      "message": "系統錯誤:無法取得未來一週資料。"
    }
  ],
  "data": null
}

更適當的回饋

{
  "data": {
    "weekWeather": [
      {"day": "Monday", "condition": "Sunny", "temp": 25},
      {"day": "Tuesday", "condition": "Cloudy", "temp": 23},
      {"day": "Wednesday", "condition": "Rainy", "temp": 22}
    ]
  },
  "extensions": {
    "userMessage": "目前只能提供未來三天的天氣資訊,感謝您的諒解。"
  }
}

兩個案例的比較

從上述範例中,我們可以看出,在不良的錯誤回饋中,當出現不符合預期的情況時,系統會直接返回一個錯誤訊息,並使 datanull

而在更適當的回饋中,雖然某些查詢可能不完全符合預期,但系統仍然返回了可用的資料(例如未來三天的天氣),並通過extensions字段提供了更具描述性的訊息。

某部分資料無法獲取時,並不代表整個查詢都失敗
GraphQL 的設計理念讓開發者能夠獲得部分成功的結果,這種方式更有利於前端開發者根據返回的訊息提供更好的用戶體驗。

總結以上,如果我們將所有的情況都視為「錯誤」,將會使得問題的解析和處理變得不夠精準。
因此,我們應該更加精確地區分和描述各種不同性質的問題,而非過度使用「錯誤」這個詞彙

參考文件
200 OK! Error Handling in GraphQL
A Guide to GraphQL Errors


Recap

今日我們探討了 Vue 中錯誤處理的原則,但會發生錯誤的不僅是我們的前端應用,跟後端的溝通也需要防範錯誤發生,提前規劃良好的錯誤處理策略。GraphQL 為此規範了一套靈活、精確的錯誤處理機制,讓前端開發能夠妥善根據訊息提供更良好的使用者體驗。

GraphQL 的開放性在 Schema 設計上提供了極大的自由度,但這也要求開發者具備深厚的經驗和業務洞察,以確保設計的精準與有效性。

事實上,GraphQL Schema 的結構應該要與真實的資料結構相符,才能讓開發過程更為直觀,還能方便日後的維護工作。

探討了 Vue 和 GraphQL 的錯誤處理機制後,明日,我們將學習如何在 Vue Apollo Client 中實踐錯誤處理的策略。


上一篇
[Day20] 實戰演練:透過 GraphQL Mutation 在 Vue 中即時更新文章,打造無縫的用戶體驗
下一篇
[Day22] 錯誤處理:Vue Apollo Client 中的策略與實踐
系列文
Vue & GraphQL 探險之旅:30天,從新手村到魔王之巔31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言