在多级异步链中处理 async throws 时,最大的挑战在于**“状态碎片化”**:当某个深层环节抛出错误时,已经完成的异步操作可能已经修改了部分全局状态或持久化数据,导致系统处于一种“中间态”。
要保证状态一致性,建议采用以下四种防御式设计模式:
1. 采用“全有或全无”的原子化提交 (The Atomic Commit Pattern)
不要在异步链的中间阶段直接修改持久化状态。相反,应该先在局部变量中完成计算,最后进行一次性的、同步的状态更新。
- 做法:将所有的
await try逻辑封装在“准备阶段”,只有当所有数据都就绪后,才进入“提交阶段”。 - 优势:如果任何一级
async throws触发,由于尚未修改核心状态(如数据库或 ViewModel 属性),状态依然是纯净的。
Swift
// ❌ 错误做法:中间报错会导致状态不一致
state.user = try await fetchUser()
state.posts = try await fetchPosts() // 如果这里报错,user 已经更新,posts 却是旧的
// ✅ 推荐做法:原子化赋值
let user = try await fetchUser()
let posts = try await fetchPosts()
// 只有到这里,才会一次性更新状态
await MainActor.run {
self.state = State(user: user, posts: posts)
}
2. 使用 defer 块进行补偿与清理 (Compensating Actions)
异步链中的每一层都可能留下未关闭的资源或临时标记。defer 即使在抛出错误时也会执行,是保证一致性的重要防线。
Swift
func processOrder() async throws {
state.isProcessing = true
// 无论函数是正常结束还是 try 抛错退出,都会恢复状态
defer { state.isProcessing = false }
try await stepOne()
try await stepTwo()
}
3. 利用结构化并发的任务取消 (Cooperative Cancellation)
在多级链条中,如果第 2 级报错,第 3、4 级如果不感知取消状态,可能会继续后台运行(僵尸任务),造成数据竞争或逻辑混乱。
- 防御点:在多级链的入口或耗时操作前,显式调用
try Task.checkCancellation()。 - 一致性保障:确保错误发生后,链条中的所有“遗留任务”都能迅速感知并停止,避免对状态造成二次破坏。
4. 状态机与“最后一次成功”快照
对于极其复杂的异步链(如分段上传、多步同步),建议引入显式状态机,并配合快照机制。
- 回滚机制:在异步链开始前,保留一份状态的副本。
- 错误捕获与恢复:在最顶层捕获错误,并根据错误类型决定是“回滚到快照”还是“停留在当前检查点”。
Swift
let snapshot = self.state // 记录快照
do {
try await complexChain()
} catch {
// 发生错误,状态回滚,保证一致性
self.state = snapshot
Logger.log("操作失败,已回滚: (error)")
throw error // 继续向上抛出
}
5. 统一错误屏障 (Error Boundary)
在多层异步调用中,不要在每一层都写 do-catch。这会导致逻辑碎片化。
-
分层原则:
- 低层 (Service) :只管
throws,不处理状态。 - 中层 (UseCase/Manager) :负责逻辑组合,使用
defer保证资源清理。 - 高层 (ViewModel/UI) :设立唯一的 Error Boundary,统一捕获错误并更新 UI 状态(如显示弹窗、恢复按钮状态)。
- 低层 (Service) :只管
总结:多级异步链防御清单
- 先计算,后赋值:避免中间状态污染。
- 善用
defer:确保 UI 状态(如 Loading)和资源句柄在错误时能正确复位。 - 检查取消:防止已失败链条的后续任务继续修改状态。
- 必要时回滚:对于关键业务,保留操作前的快照。
通过这种“局部准备 -> 集中提交 -> 统一清理”的模式,可以极大降低 async throws 在复杂链路中造成状态破坏的风险。