我正在尝试重构一些代码,目前我正在将Combine发布者与回调闭包混合在一起。
该代码向REST端点发出登录请求。在失败时,我希望函数的调用者接收错误有效载荷(挑战),在成功时,调用者无需执行任何操作。因此,这是一种奇怪的情况,其中呼叫者仅对该错误感兴趣。
目前,我正在这样做(简化代码):
import Foundation
import Combine
struct Challenge {
let id: Int
}
struct MyError: Error {
let challenge: Challenge?
}
class UserManager {
@Published var user: String?
private func doRequest(username: String, password: String) -> AnyPublisher<String, MyError> {
// In the real world this does a request to a REST API, returning either a User object or an error containing a Challenge.
return Fail(outputType: String.self, failure: MyError(challenge: Challenge(id: 1)))
.eraseToAnyPublisher()
}
func login(username: String, password: String, next: @escaping (Challenge) -> ()) -> AnyCancellable {
doRequest(username: username, password: password)
.catch { error -> AnyPublisher<String, Never> in
if let challenge = error.challenge {
next(challenge)
}
return Empty().eraseToAnyPublisher()
}
.sink(
receiveValue: { [weak self] user in
self?.user = user
}
)
}
}
let userManager = UserManager()
var anyCancellables = Set<AnyCancellable>()
userManager.login(username: "hello", password: "world") { challenge in
print("received challenge!")
}
.store(in: &anyCancellables)
如您所见,login
函数使用一个名为next
的参数,发布者自己处理sink
运算符内的值。将发布者与像这样的闭包结合起来感觉并不好,理想情况下,我希望login
函数具有以下签名:
func login(username: String, password: String) -> AnyPublisher<Challenge, Never>
但是当我想编写这样的函数时,我遇到了一个问题,即我无法在sink
函数本身中使用login
,因为这会将返回类型转换为{{ 1}}。基本上,我想将错误转化为发布者的输出,并且还要处理AnyCancellable
函数中的实际值。这怎么可能?
答案 0 :(得分:0)
好吧,我想有时候要做的只是写下一个问题,以求得解决方案的方法?
func login(username: String, password: String) -> AnyPublisher<Challenge, Never> {
doRequest(username: username, password: password)
.compactMap {
self.user = $0
return nil
}
.catch { error -> AnyPublisher<Challenge, Never> in
if let challenge = error.challenge {
return Just(challenge).eraseToAnyPublisher()
}
return Empty().eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
不确定是否使用这样的compactMap
来处理副作用确实是最好的方法,但是它确实有效。