情境:有一個變數A的初始值是 undefined,當經過 http request 後,若是失敗則需要重試,若是重試3次失敗則通知重試失敗錯誤訊息,須 request 成功後才賦予變數 A 的值為 0 或 1。
問題是,變數 A 有三種可能性,應該將其當作狀態嗎?
先透過 xstate.js 來描述這樣的狀態機,我們可以建立一個狀態機,其中包括初始狀態、重試狀態、成功狀態和失敗狀態。以下是一個簡單的範例:
import { createMachine, interpret } from 'xstate';
// 定義狀態機
const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
context: {
retries: 0,
A: undefined,
},
states: {
idle: {
on: {
FETCH: 'loading'
}
},
loading: {
invoke: {
src: 'fetchData',
onDone: {
target: 'success',
actions: 'setData'
},
onError: {
target: 'retry',
actions: 'incrementRetries'
}
}
},
retry: {
always: [
{
target: 'failure',
cond: 'maxRetriesReached'
},
{ target: 'loading' }
]
},
success: {
type: 'final'
},
failure: {
type: 'final'
}
}
}, {
actions: {
incrementRetries: (context) => context.retries++,
setData: (context, event) => context.A = event.data
},
guards: {
maxRetriesReached: (context) => context.retries >= 3
}
});
// 模擬 fetchData 函數
const fetchData = () => {
return new Promise((resolve, reject) => {
// 模擬 HTTP request
setTimeout(() => {
// 假設 50% 的機率成功,50% 的機率失敗
Math.random() > 0.5 ? resolve(Math.random() > 0.5 ? 1 : 0) : reject('Failed');
}, 1000);
});
};
// 創建服務
const fetchService = interpret(fetchMachine.withConfig({
services: {
fetchData: fetchData
}
})).onTransition(state => {
console.log(state.value, state.context);
});
// 啟動服務
fetchService.start();
// 發送 FETCH 事件開始請求
fetchService.send('FETCH');
這段代碼建立了一個狀態機,其邏輯如下:
idle
,接收到 FETCH
事件後進入 loading
狀態。loading
狀態中,執行 fetchData
請求,請求成功則進入 success
狀態,並將變數 A
設置為 0 或 1;請求失敗則進入 retry
狀態,並增加重試次數。retry
狀態中,如果重試次數達到 3 次,則進入 failure
狀態,否則返回 loading
狀態重新嘗試。success
和 failure
狀態是終結狀態,表示狀態機的運行結束。從上述例子可以看出變數 A 其實並不適合作為狀態機中的狀態。
在設計狀態機時,辨別是狀態還是數據是一個關鍵問題。以下是一些指導原則,可以幫助你做出這個區分:
idle
: 系統閒置等待請求。loading
: 系統正在發送請求。retry
: 系統正在處理重試邏輯。success
: 請求成功。failure
: 請求失敗。retries
: 記錄重試的次數,是一個可變的計數器。A
: 請求返回的數據,是請求結果的具體信息。設計良好的狀態機能夠使系統的行為更加清晰和易於管理。以下是一些設計原則,可以幫助你明確識別和定義狀態:
每個狀態應該有明確的單一責任,即它應該只負責一個特定的行為或任務。如果某個狀態需要負責多個不同的行為,考慮將其拆分成多個獨立的狀態。
每個狀態之間的轉移應該是明確的和有意圖的。應避免不必要的狀態轉移,並確保每個轉移都有合理的觸發條件。
狀態機應該只包含有限的狀態。過多的狀態會增加系統的複雜性,難以管理和維護。應盡量保持狀態數量在合理範圍內。
狀態應該是原子的,即在任何時刻系統應該只處於一個明確的狀態中。避免狀態重疊或多重狀態,這會導致系統行為不確定。
在每個狀態中,系統的行為應該是一致的。這意味著在特定狀態下系統應該總是執行相同的操作,並對相同的事件做出一致的反應。
狀態應該描述系統的行為,而數據(如 context
)應該描述狀態機運行時的數據。這有助於保持狀態定義的清晰和簡潔,並使數據管理更加靈活。
確保每個狀態和狀態轉移都是可觀察的,即可以在運行時監控和記錄狀態機的行為。這對於調試和監控系統運行非常重要。
狀態應該是可測試的。設計狀態機時應考慮如何對每個狀態和狀態轉移進行單元測試,以確保系統行為符合預期。
保持狀態機的設計簡單。避免過於複雜的狀態結構和不必要的狀態轉移。簡單的狀態機更容易理解和維護。
通過具體的業務需求和使用案例來分析系統需要哪些狀態。對每個狀態進行詳細分析,確保它們能夠覆蓋所有業務場景。
以下是上述原則在 HTTP Request 狀態機中的應用示例:
loading
狀態負責處理 HTTP 請求。retry
狀態負責處理重試邏輯。success
狀態表示請求成功。failure
狀態表示請求失敗。idle
-> loading
(接收到 FETCH
事件)loading
-> success
(請求成功)loading
-> retry
(請求失敗)retry
-> loading
(重試次數未達到限制)retry
-> failure
(重試次數達到限制)idle
, loading
, retry
, success
, failure
)loading
狀態中,系統總是嘗試發送 HTTP 請求。context
中,而不是狀態中。通過應用這些設計原則,可以有效地識別和定義狀態,從而構建出清晰且可維護的狀態機。
通過這些指導原則,你可以更清晰地區分狀態和數據,並設計出更簡潔和高效的狀態機。