cache-then-network 是一个面向 Kotlin 协程 Flow 的数据流模式说明,属于 maxrave-dev/kotlin-footguns 技能集合。其核心目标是:在同一个仓库方法中,先立即向调用方提供已存储的缓存副本,随后再提供从网络获取的新鲜数据;只有当缓存和网络都没有成功提供内容时,才向调用方发出错误。
用途与适用场景 该模式适用于希望界面先显示已有内容、再被网络结果更新的场景,例如应用启动即展示上次保存的列表、详情页先渲染缓存再刷新。它始终会请求网络,因此与“缓存未过期则直接返回、不发起请求”的过期缓存策略不同。文中指出,选择哪种策略应按每个方法的请求次数成本来判断,而非按数据允许的陈旧程度判断:每次打开页面仅一次请求时,本模式更合适;按键解析、每次屏幕触发多次请求的场景则更适合过期缓存策略。
主要能力与关键顺序 示例方法返回 Flow<Resource<Catalog>>,在 flow 构建器内运行,并切换到 Dispatchers.IO。三项顺序决策是该模式的关键:第一,缓存值在请求开始之前发出,而非与请求竞速;第二,新鲜值在持久化之前发出,使用户先拿到内容,磁盘写入只做记账、其延迟不影响用户;第三,仅在成功时写入缓存,失败请求不能覆盖已有的良好副本。
错误处理与解码容错 发出错误必须以避免覆盖已显示缓存为前提:若已发出缓存,网络失败不应再发出错误,否则正常显示的界面会在网络不稳定时一秒后切换到错误状态;该守卫应以实际已发出的本地 cached 变量为键,而非缓存开关或存储字符串是否非空,以免失步。解码失败必须降级为缓存未命中而不能崩溃:存储副本视为不可信输入,用 runCatching { … }.getOrNull() 使其退化为冷启动行为,从而正确重新启用错误路径;文中提到配套的宽松解码设置位于 ttl-keyed-json-cache-lenient-decode。
双发射的后果与限制 缓存与新鲜数据都是相同的 Success 类型,调用方无法区分陈旧与新鲜。这是有意的简化,但调用方需承担后果:若界面在每次发射时重置滚动位置、重启动画或重跑昂贵计算,将每次打开执行两次;应将这类反应做成幂等,或在信封中加入新鲜度标记,而不能通过丢弃第一次发射来“修复”,那会删除该功能本身。
验证方式 正常路径下故障不可见,应直接测试异常组合:热缓存且网络不可达时,收集结果列表必须恰含一个 Success,出现 Error 即为缺陷;冷缓存且网络不可达时,列表必须恰含一个 Error,为空则说明守卫反了或被外层捕获吞掉;热缓存且网络可达时,应为两个 Success,顺序为先缓存后新鲜。断言应针对发射列表而非最终值;若收集方合并状态或本地读取过快导致缓存发射未被渲染,应在 UI 层而非仓库层测量。