Swift 合并:将错误转化为发布者的输出
Swift Combine: turn error into the publisher's output
我正在尝试重构一些代码,我目前正在将 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>
但是当我想写这样一个函数时,我 运行 遇到了我不能在 login
函数本身中使用 sink
的问题,因为这会转换return 输入 AnyCancellable
。基本上我想将错误转化为发布者的输出,并处理 login
函数中的实际值。这怎么可能?
好吧,我想有时候只需要写下一个问题就可以解决它
func login(username: String, password: String) -> AnyPublisher<Challenge, Never> {
doRequest(username: username, password: password)
.compactMap {
self.user = [=10=]
return nil
}
.catch { error -> AnyPublisher<Challenge, Never> in
if let challenge = error.challenge {
return Just(challenge).eraseToAnyPublisher()
}
return Empty().eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
不完全确定使用这样的 compactMap
来处理副作用是否真的是最好的方法,但它确实有效。
我正在尝试重构一些代码,我目前正在将 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>
但是当我想写这样一个函数时,我 运行 遇到了我不能在 login
函数本身中使用 sink
的问题,因为这会转换return 输入 AnyCancellable
。基本上我想将错误转化为发布者的输出,并处理 login
函数中的实际值。这怎么可能?
好吧,我想有时候只需要写下一个问题就可以解决它
func login(username: String, password: String) -> AnyPublisher<Challenge, Never> {
doRequest(username: username, password: password)
.compactMap {
self.user = [=10=]
return nil
}
.catch { error -> AnyPublisher<Challenge, Never> in
if let challenge = error.challenge {
return Just(challenge).eraseToAnyPublisher()
}
return Empty().eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
不完全确定使用这样的 compactMap
来处理副作用是否真的是最好的方法,但它确实有效。