restate / v1.0.0
可靠性設計
當儲存空間緩慢、出錯或不存在時 restate 保證什麼,以及如何解讀它回報的錯誤。
保證
這些行為屬於 1.0 合約的一部分,並由套件的測試涵蓋。
| 情況 | restate 的做法 |
|---|---|
連續多次儲存 | 依 store 依序執行;在佇列中等待的儲存會寫入最新的值。 |
某筆紀錄載入失敗 | autoSave 和批次儲存會以 LOAD_FAILED 略過它;只有明確的 save() 能取代它。 |
儲存途中元件被卸載 | 已要求的儲存會完成;之後對已銷毀 store 的寫入會被忽略。 |
儲存空間無法使用 | store 在記憶體中運作,status().available 為 false,儲存會被拒絕。 |
轉接器卡住 | 操作在 operationTimeoutMs 之後逾時,後續工作會等到轉接器結束。 |
待處理的工作太多 | 新的操作會以 QUEUE_FULL 被拒絕,而不是無限增加。 |
依序儲存
同一個 store 的操作絕不會重疊。在另一次儲存等待時發出的儲存會取用較新的值,所以就算每次變化都儲存,也能寫入最新狀態而不會塞滿佇列。
useDraft.set('A');
const first = useDraft.save(); // writes "A"
useDraft.set('B');
const second = useDraft.save(); // waits in the queue
useDraft.set('C');
const third = useDraft.save(); // joins the waiting save
await Promise.all([first, second, third]); // ['A', 'C', 'C'] — two writes動手試試
讓儲存空間變慢、讓寫入失敗,或乾脆移除儲存空間。
正在準備範例…
載入失敗時
restateManager.ready 會以 BULK_OPERATION_FAILED 被拒絕,並列出每個失敗的 store。在那筆紀錄成功載入前,autoSave 和 saveAll 不會取代它,而是回報 LOAD_FAILED。
用 initialize() 重試以保留並儲存你的修改、呼叫 restore() 捨棄修改,或呼叫 save() 刻意覆寫該紀錄。
try {
await restateManager.ready;
} catch (error) {
// BULK_OPERATION_FAILED: one entry per store that could not load.
for (const { store, error: cause } of error.failures) {
console.error(store.key, cause.code);
}
}儲存並關閉
卸載前要求的儲存仍會完成,所以「儲存並關閉」的處理函式不需要等待。
function Document({ onClose }) {
const useDraft = useRestateScope((scope) => persistedRestate('', { scope, key: 'draft' }));
const saveAndClose = () => {
useDraft.save(); // finishes even after this component unmounts
onClose();
};
// …
}驗證與遷移已儲存的資料
version、migrate 和 validate 能保護你不受舊紀錄或損毀紀錄影響。validate 會在儲存前,以及還原或遷移後執行。
import { persistedRestate } from '@kkwaenggwari/restate';
const useSettings = persistedRestate(
{ theme: 'system' },
{
key: 'settings',
version: 2,
migrate(value, from, to) {
if (from === 1 && to === 2 && typeof value?.darkMode === 'boolean') {
return { theme: value.darkMode ? 'dark' : 'light' };
}
throw new Error('Unsupported stored version');
},
validate: (value) => ['system', 'light', 'dark'].includes(value?.theme),
},
);自備儲存空間
任何具有 getItem、setItem 和 removeItem 的物件都可以。方法可以回傳值或 Promise;isAvailable 是選用的。
import { persistedRestate } from '@kkwaenggwari/restate';
const sessionStorageAdapter = {
isAvailable: () => typeof sessionStorage !== 'undefined',
getItem: (key) => sessionStorage.getItem(key),
setItem: (key, value) => sessionStorage.setItem(key, value),
removeItem: (key) => sessionStorage.removeItem(key),
};
export const useFilters = persistedRestate(
{ query: '' },
{
key: 'filters',
storage: sessionStorageAdapter,
},
);限制與逾時
調整單一操作可以執行多久、可以有多少操作排隊,以及值最大可以多大。
persistedRestate(value, {
key: 'draft',
operationTimeoutMs: 10_000, // default 30000
maxPendingOperations: 64, // default 256
maxStorageCharacters: 1_000_000,
});錯誤代碼
restate 建立的錯誤名稱為 RestateError,帶有 code,持久化 store 的錯誤還帶有 key。儲存轉接器和你的回呼可能會以它們自己的錯誤拒絕。
| 代碼 | 意義 |
|---|---|
BULK_OPERATION_FAILED | restateManager.ready 或 saveAll 等批次呼叫發生失敗;請查看 error.failures。 |
INDEXEDDB_ABORTED | IndexedDB 交易被中止。 |
INDEXEDDB_BLOCKED | 開啟資料庫時被另一個分頁中的連線擋住。 |
INDEXEDDB_CONNECTION_CLOSING | 請求開始時資料庫連線正在關閉。 |
INDEXEDDB_OPEN_FAILED | 無法開啟資料庫。 |
INDEXEDDB_REQUEST_FAILED | IndexedDB 請求失敗。 |
INDEXEDDB_STORE_MISSING | 已建立的資料庫中沒有這個物件存放區。 |
INVALID_STORAGE_RECORD | 已儲存的紀錄不是有效的 restate 紀錄。 |
INVALID_STORED_VALUE | 值沒有通過 validate。 |
LIFECYCLE_LIMIT | 某個 updater 不斷地啟動又停止。 |
LOAD_FAILED | 紀錄無法載入,因此自動儲存或批次儲存沒有取代它。 |
MMKV_UNAVAILABLE | 無法載入 react-native-mmkv。 |
MULTIPLE_ERRORS | 一次操作中有多個回呼失敗;請查看 error.errors。 |
NOTIFICATION_LIMIT | 觀察者在迴圈中不斷安排新的變化。 |
OBSERVER_ERROR | 某個監聽器或生命週期回呼拋出了例外。 |
OPERATION_ABORTED | 某個儲存操作被取消。 |
QUEUE_FULL | 等待中的操作超過 maxPendingOperations 允許的數量。 |
REENTRANT_SETTER | 某個狀態 updater 在執行時試圖修改同一個 store。 |
SCOPE_DESTROYED | 使用已銷毀的 scope 建立 store。 |
SEQUENCE_EXHAUSTED | 內部序列的識別碼已用完。 |
STORAGE_BLOCKED | 逾時或中止後,先前的轉接器呼叫仍未結束。 |
STORAGE_TIMEOUT | 操作耗時超過 operationTimeoutMs。 |
STORAGE_UNAVAILABLE | 這裡無法使用儲存空間;store 會繼續在記憶體中運作。 |
STORAGE_VALUE_TOO_LARGE | 編碼後的值超過 maxStorageCharacters。 |
STORAGE_VERSION_MISMATCH | 紀錄的版本不同,而且沒有處理它的 migrate 函式。 |
STORE_DESTROYED | 訂閱了已銷毀的 store,或要求它使用儲存空間。 |