TanStack Query 缓存持久化报 Uncaught in promise,检查保存拒绝链
缓存更新成功后,自定义持久化仍可能拒绝。区分保存、脱水回调和内置适配器的错误处理,避免把未落盘显示为已保存。
页面查询正常,给 TanStack Query 接上自定义持久化后,更新缓存却出现 Uncaught (in promise)。错误栈指向 IndexedDB 写入或脱水回调,而包住 setQueryData() 的同步 try/catch 没有接到错误时,应检查缓存订阅发起的保存 Promise。
@tanstack/query-persist-client-core 5.104.5 的 persistQueryClientSubscribe() 在缓存事件回调里调用异步的 persistQueryClientSave(),没有等待或捕获它的拒绝。保存失败因此可能脱离业务调用的错误链;查询请求本身成功,也不代表缓存已写入磁盘。
先区分哪一层拒绝
自定义 persister.persistClient() 返回 rejected Promise,或 dehydrateOptions 中自己的筛选等回调抛错,都能进入这条路径。后一种错误发生在构造待保存状态时,甚至还没有调用存储适配器。
内置 createAsyncStoragePersister() 和 createSyncStoragePersister() 已在保存适配器内捕获序列化与存储写入错误,并提供 retry 路径。不能仅看到 QuotaExceededError,就断言所有内置适配器都会造成同一种未处理拒绝。还要检查自定义重试函数是否抛错、脱水阶段是否失败,以及实际用的是哪个 persister。
同步代码外的捕获不会沿着被丢弃的 Promise 工作:
try {
queryClient.setQueryData(['profile'], profile);
} catch (error) {
// 不能依靠这里接住订阅器稍后发生的异步保存失败
}
这里的 queryClient、profile 是应用已有对象,示例只说明捕获边界,不是完整初始化代码。
自定义存储要有自己的失败结果
如果失败来自自己编写的 IndexedDB persister,可在它的异步 persistClient() 内等待写入并捕获错误。将失败送到应用已有的错误状态或通知通道,明确标记“本次未持久化”,再决定是否关闭后续保存、清理可丢弃缓存或有限重试。不要只写一个空 catch,让离线界面继续显示“已保存”。
存储写入成功不能代替业务提交成功;反过来,持久化失败也不一定需要撤销已经成功的服务端更新。应按数据用途区分:可重新获取的查询缓存可以放弃这次落盘,离线待提交操作则需要另有可靠队列和明确失败提示。
脱水回调失败要在脱水层处理。检查 shouldDehydrateQuery 等自定义函数所接收的数据,避免访问不存在的字段;不要指望只包住 storage.setItem() 就能捕获发生在它之前的错误。
若应用自行接管缓存订阅与保存调度,应对每次 persistQueryClientSave() 返回的 Promise 捕获错误,同时保留需要的节流、卸载取消订阅和保存顺序。不要并行保留原订阅器,造成双重写入;也不要给当前接口传入并不存在的 onPersistError 参数,误以为错误已经接管。
最后分别检查正常保存、存储拒绝和脱水回调异常。失败时应有可识别状态,控制台没有漏出的未处理拒绝;成功时还要在重新打开页面后验证恢复出的缓存。仅消除控制台提示,不能证明离线数据可恢复。


