1️⃣ 问题背景
传统异步网络请求链常见写法:
fetchUser { user in
fetchPosts(user.id) { posts in
fetchComments(posts) { comments in
print(comments)
}
}
}
问题:
- 回调地狱,嵌套多层
- 不可组合,无法在不同请求间自由组合
- 副作用分散,难以测试
2️⃣ Functor / Monad 思想
-
Functor (
map)- 对容器(异步值、Publisher、Result 等)中的值应用函数
- 不改变容器结构
- 适合对结果做同步转换
-
Monad (
flatMap)- 对容器值应用返回容器的函数
- 可以链式组合多个异步操作
- 自动“平铺嵌套容器”,保持类型安全
- 遇到失败自动短路(Result/Publisher)
核心思想:把异步请求视作 容器,函数返回值也是容器 → 链式组合
3️⃣ 使用 Result / Combine 重构异步请求
(1) 使用 Result 封装异步操作
import Foundation
import Combine
struct User { let id: Int }
struct Post { let id: Int }
struct Comment { let id: Int }
func fetchUser() -> AnyPublisher<User, Error> {
Just(User(id: 1))
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
func fetchPosts(userId: Int) -> AnyPublisher<[Post], Error> {
Just([Post(id: 10), Post(id: 11)])
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
func fetchComments(posts: [Post]) -> AnyPublisher<[Comment], Error> {
Just(posts.map { Comment(id: $0.id * 100) })
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
每个函数返回 Publisher → 异步容器
(2) 链式组合 (Monad flatMap)
let cancellable = fetchUser()
.flatMap { user in
fetchPosts(userId: user.id) // flatMap 绑定下一步异步操作
}
.flatMap { posts in
fetchComments(posts: posts)
}
.sink { completion in
switch completion {
case .finished: break
case .failure(let error): print("Error:", error)
}
} receiveValue: { comments in
print("Fetched comments:", comments)
}
优势:
- 链式组合 → 可插入任意异步操作
- 类型安全 → 输出类型清晰
- 错误传播 → 遇到失败自动短路,进入
sink的 failure 分支 - 副作用集中 →
sink/assign
(3) 同步 Functor map
如果想对数据做同步转换:
let processed = fetchUser()
.map { user in user.id * 100 } // Functor map
.flatMap { id in fetchPosts(userId: id) }
map对 Publisher 内值做同步计算flatMap对 Publisher 内值做异步操作 → Monad
4️⃣ 测试友好性
- 纯函数封装网络请求:
func mockFetchUser() -> AnyPublisher<User, Error> {
Just(User(id: 42))
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
- 纯函数 → 可单元测试
- 无副作用,外部 sink 可用测试用例捕获结果
- 链式组合可拆分测试:
let testPosts = fetchPosts(userId: 42)
.sink { completion in
...
} receiveValue: { posts in
assert(posts.count == 2)
}
- 每一步都可以独立测试
- 保持高可组合性
5️⃣ 总结设计模式
| 概念 | 应用到异步请求链 |
|---|---|
Functor (map) | 对 Publisher 内部值做同步转换 |
Monad (flatMap) | 链式组合异步请求,平铺容器 |
| 错误传播 | Publisher 内错误自动短路,进入 sink 的 failure |
| 副作用控制 | 所有输出 / 打印集中在 sink / assign |
💡 核心思想:
将异步请求视作 容器 → map / flatMap 链式组合 → 保持纯函数 → 副作用集中 → 可测试、可组合