我有一个连接到PassthroughSubject
的按钮,它触发了网络负载。我遇到的问题是,如果网络请求失败(或者飞行前验证失败),则PassthroughSubject
已完成:
private let createListSubject = PassthroughSubject<Void, Never>()
点击按钮后,我会通过send(())
向该主题发送一个新事件。
// Later on, in some function I set up the subject
private func setupCreateListSubject() {
self.createListSubject
.combineLatest(self.$listName, self.$selectedClients)
.tryMap { [weak self] (_, listName, selectedClients) -> (String, [String]) in
let clients = Array(selectedClients)
try self?.validate(listName: listName, selectedClients: clients)
return (listName, clients)
}
.flatMap { [clientListCreator] (listName, selectedClients) -> AnyPublisher<Result<ClientListMembersDisplayable, Error>, Error> in
return clientListCreator.createClientList(listName: listName, listMemberIds: selectedClients)
}
.catch { error in
return Future<Result<ClientListMembersDisplayable, Error>, Error> { // <-- One of the problems is that Future completes after 1 event
$0(.success(.failure(error)))
}
}
.sink(receiveCompletion: { [weak self] completion in
switch completion {
case .failure(let error):
self?.errorAlertContext = AlertContext(title: error.localizedDescription)
case .finished:
break
}
}, receiveValue: { [weak self] result in
switch result {
case .failure(let error):
self?.errorAlertContext = AlertContext(title: error.localizedDescription)
case .success:
self?.errorAlertContext = nil
}
}).store(in: &self.disposeBag)
}
有两个问题:
tryMap
中验证失败时,将调用catch
我可以使用replaceError
来解决这两个问题,但是我想要的是将发布商的错误转换成Result
错误和的成功{ 1}}不会收到任何完成事件(因为用户将来仍希望点击该按钮。
执行此操作的合并方法是什么?
我认为我想要的是类似于createListSubject
的东西,但是收到了旧错误并返回了成功的结果。
答案 0 :(得分:1)
模式是将其包装在flatMap
中,因此您可以通过产生一个输出为Result<..., Error>
且失败为Never
的新发布者来处理每个值:
createListSubject
.combineLatest(self.$listName, self.$selectedClients)
.flatMap { [weak self] (_, listName, selectedClients) -> AnyPublisher<Result< ClientListMembersDisplayable, Error>, Never> in
Just(())
.tryMap {
try self?.validate(listName: listName, selectedClients: selectedClients)
}
.flatMap {
// I'm assuming this returns AnyPublisher<Result<ClientListMembersDisplayable, Error>, Error>
clientListCreator.createClientList(listName: listName, listMemberIds: selectedClients)
}
.catch { err -> AnyPublisher<Result<ClientListMembersDisplayable, Error>, Never>
Just(.failure(err))
}
.eraseToAnyPublisher()
}
.sink (...)
.store(in: &self.disposeBag)
答案 1 :(得分:0)
为清楚起见,您没有发布任何完成createListSubject
的代码。您在createListSubject
的下游构建新的发布者,这些下游的发布者可能会失败。
您在catch
区块的正确轨道上。您的catch
块将上游故障替换为新发布者的输出。问题在于您的新发布者是Future
,它仅发布一个输出然后完成。
我们要做的是发送该输出,然后再次追加整个管道,如下所示:
.catch {
Just(.failure($0))
.append(theWholePipelineAgain())
}
这意味着我们需要将setupCreateListSubject
分为两部分。一部分返回管道(直到catch
,包括sink
),另一部分仅在管道上调用catch
。
由于我们使用Never
将上游故障转换为正常输出,因此可以将Failure
用作管道的private func createListPublisher() -> AnyPublisher<Result<ClientListMembersDisplayable, Error>, Never> {
createListSubject
.combineLatest(
$listName,
// Here I'm using .map to turn Set<String> into [String] so I don't have to do it in tryMap later.
$selectedClients
.map { Array($0) }
)
.tryMap { [weak self] (_, listName, selectedClients) in
// Here I'm creating a tuple and then returning just its .1 element, to let Swift
// infer the return type.
(
try self?.validate(listName: listName, selectedClients: selectedClients),
(listName, selectedClients)
).1
}
.flatMap { [clientListCreator] in
clientListCreator.createClientList(listName: $0, listMemberIds: $1)
}
.catch { [weak self] in
Just(.failure($0))
.append(
// THIS IS WHERE THE MAGIC HAPPENS
self?.createListPublisher().eraseToAnyPublisher()
?? Empty().eraseToAnyPublisher()
)
}
.eraseToAnyPublisher()
}
类型。这是创建管道的函数:
private func subscribeToCreateListPublisher() {
createListPublisher()
.sink { [weak self] in
switch $0 {
case .failure(let failure):
self?.errorAlertContext = AlertContext(title: failure.localizedDescription)
case .success(_):
self?.errorAlertContext = nil
}
}
.store(in: &disposeBag)
}
这是订阅管道的函数:
Failure
由于管道尚未完成,并且具有Never
类型的receiveCompletion:
,因此我们不再需要为sink
赋予{{1}}块。