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,或要求它使用存储。 |