錯誤處理是軟體開發中的關鍵部分。
隨著現代前端框架的迅速發展,如何在 Vue 中策略性地、有效地處理各種錯誤—無論是預期還是非預期的—已成為每位開發者的核心技能。
此外,GraphQL 強調 Schema 和 Type 的嚴格性,對於錯誤的定義和格式也有明確的規範,確保前後端在錯誤處理上能夠達到高度的一致性與穩定性。
在這篇文章中,我們將深入探討 Vue 和 GraphQL 中的錯誤處理策略以及實踐基礎,一同打造更穩固、更韌性的 Vue 應用。
(謎之音:錯誤處理做得好,解釋 Bug 沒煩惱~)
還記得大學時期熬夜趕報告的日子嗎?可能努力地敲了好幾個小時,結果...
「糟糕!」
Word 突然當機,所有的努力似乎都白費了。
但後來,微軟加入了錯誤恢復功能,當 Word 再次開啟時,它會提示您恢復未保存的文件。
這不僅讓使用者鬆了一口氣,更是一個鮮明的例子:錯誤處理真的很重要。
畢竟,誰都不想再有「哎呀,我剛才有存檔嗎?」這種心跳加速的瞬間。
錯誤的分類與管理是一個關鍵的概念。
尤其越大型、複雜的專案,越需要建立一套合適的錯誤處理架構。
但從基本角度來看,應用程式的錯誤主要可以劃分為兩大類:預期錯誤與非預期錯誤。
那些可以被開發者預測到的錯誤,被歸類為「可預期的錯誤」。
例如:使用者輸入了無效的電子郵件地址或密碼、輸入的付款卡號不正確,或選擇了一個已被預訂的日期。
對於這些錯誤,我們可以給予使用者明確且具指導性的回饋,幫助他們進行正確操作。
「非預期的錯誤」往往源於未知的因素或外部依賴的問題,例如伺服器突然中斷服務。
面對這類錯誤,我們的首要任務是確保它們不會大幅地影響使用者的體驗,並給予使用者一個基本的通知,讓他們知道目前存在某些問題。
使用者體驗,是指使用者在使用產品或服務時的整體感受和互動體驗。
應用穩定性,則是描述軟體在執行時的可靠性和持續性,沒有出現非預期的問題或崩潰。
一個好的應用,不僅要功能齊全、介面漂亮,最重要的是操作便利順暢,讓使用者不會因為某些錯誤而感到困擾。
畢竟,若失去使用者的信賴,他們可能再也不回來了。
使用者體驗涉及專門的 UI/UX 設計知識,而確保應用穩定性,就要依賴工程師的細心與熟練技巧。
任何語言的錯誤處理,基本上都從 try & catch 開始。
作為 JavaScript 的框架 — Vue 當然也不例外。
更進一步,我們還可以利用 Vue 的 生命週期鉤子 (Lifecycle Hook) 和 事件監聽器 (Event Listener) 來捕捉和處理錯誤。
Vue 提供了 errorCaptured 鉤子 (hook),它就像 React 中的錯誤邊界 (Error Boundaries)。
當 Component 捕捉到 Child Component 報出的錯誤時,這個鉤子會被觸發。
開發者可以在這裡進行錯誤的日誌記錄、報告、提供某些備用 UI,或是向上傳遞錯誤,使其可以繼續被上層 Component 的 errorCaptured 或全局錯誤處理捕捉到。
錯誤捕捉的來源有:
參考文件
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');
Vue 提供了 Component 層級的錯誤處理,也有全局的錯誤處理,接著讓我們來看看當錯誤發生時,Vue 的傳遞與處理流程:
app.config.errorHandler
(當然,這是基於開發者已經設定了這個功能的前提下)。errorCaptured
,系統會從最底層的子組件往上逐一觸發。errorCaptured
內出錯了,那麼它抓到的錯誤和自己製造的錯誤,兩個都會送到 app.config.errorHandler
。errorCaptured
回傳 false
。了解基本的錯誤處理機制後,我們能夠設計更多元的錯誤處理策略,進而提升使用者體驗和應用的穩定性。
緊接著,讓我們來探討另一主角 - GraphQL 的錯誤處理機制。
GraphQL 設計的初衷是讓前後端的資料需求和溝通更加靈活和精確。
當我們使用 GraphQL 進行資料請求時,有時候可能會碰到某些錯誤。
為了讓前端能夠更好地理解和處理這些錯誤,GraphQL 規範制定了一個明確的錯誤回應格式。
GraphQL 跟 RESTful 一個很明顯的差異,就是 HTTP Status Code 200 不等於正常
GraphQL 的規範故意沒有提到 HTTP,因為 GraphQL 是不依賴於傳輸協議的。規範的實現者可以選擇實施規範的傳輸機制。
因此,GraphQL 伺服器在傳遞錯誤時不依賴 HTTP 狀態碼。以下是一個同時包含 data
和 errors
的 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 Spec 的 Response 章節中,詳細規範著 Response 該有的格式。
基本格式:GraphQL 的回應必須是一個 map。
錯誤欄位 (errors
):
errors
的項目。資料欄位 (data
):
data
的項目。擴展欄位 (extensions
):
extensions
的項目。確保相容性:為了確保未來的協議改變不會打破現有的服務和客戶端,頂層的回應映射不應該包含上述三者以外的任何項目。
序列化提示:當回應中包含 errors
時,為了方便調試,建議在序列化時使其排在第一位,以清楚顯示回應中存在的錯誤。
GraphQL 規範中也進一步制定了 Data 與 Error 項目的限制:
一個包含錯誤的 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"
}
}
]
}
讓我們深入探討這個範例中隱含的規範涵義:
錯誤列表:當回應中包含 errors
欄位時,表示請求中發生了至少一個錯誤。這個列表內會包含一個或多個錯誤對象。
資料與錯誤的關係:
data
欄位,那麼 errors
列表必須包含至少一個錯誤,說明為何沒有資料返回。data
欄位,那麼 errors
可以包含在執行過程中產生的任何錯誤。請求錯誤:
欄位錯誤:
為了達成一致性的傳遞與處理錯誤,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": "目前只能提供未來三天的天氣資訊,感謝您的諒解。"
}
}
從上述範例中,我們可以看出,在不良的錯誤回饋中,當出現不符合預期的情況時,系統會直接返回一個錯誤訊息,並使 data
為 null
。
而在更適當的回饋中,雖然某些查詢可能不完全符合預期,但系統仍然返回了可用的資料(例如未來三天的天氣),並通過extensions字段提供了更具描述性的訊息。
某部分資料無法獲取時,並不代表整個查詢都失敗
GraphQL 的設計理念讓開發者能夠獲得部分成功的結果,這種方式更有利於前端開發者根據返回的訊息提供更好的用戶體驗。
總結以上,如果我們將所有的情況都視為「錯誤」,將會使得問題的解析和處理變得不夠精準。
因此,我們應該更加精確地區分和描述各種不同性質的問題,而非過度使用「錯誤」這個詞彙
參考文件
200 OK! Error Handling in GraphQL
A Guide to GraphQL Errors
今日我們探討了 Vue 中錯誤處理的原則,但會發生錯誤的不僅是我們的前端應用,跟後端的溝通也需要防範錯誤發生,提前規劃良好的錯誤處理策略。GraphQL 為此規範了一套靈活、精確的錯誤處理機制,讓前端開發能夠妥善根據訊息提供更良好的使用者體驗。
GraphQL 的開放性在 Schema 設計上提供了極大的自由度,但這也要求開發者具備深厚的經驗和業務洞察,以確保設計的精準與有效性。
事實上,GraphQL Schema 的結構應該要與真實的資料結構相符,才能讓開發過程更為直觀,還能方便日後的維護工作。
探討了 Vue 和 GraphQL 的錯誤處理機制後,明日,我們將學習如何在 Vue Apollo Client 中實踐錯誤處理的策略。