swift 合并:如何在不完成原始发布者的情况下替换/捕捉错误?

7gcisfzg  于 2023-03-11  发布在  Swift
关注(0)|答案(8)|浏览(141)

给定以下代码:

enum MyError: Error {
        case someError
    }

    myButton.publisher(for: .touchUpInside).tryMap({ _ in
        if Bool.random() {
            throw MyError.someError
        } else {
            return "we're in the else case"
        }
    })
        .replaceError(with: "replaced Error")
        .sink(receiveCompletion: { (completed) in
            print(completed)
        }, receiveValue: { (sadf) in
            print(sadf)
        }).store(in: &cancellables)

每当我点击按钮,我得到we're in the else case,直到Bool.random()为真-现在抛出一个错误。我尝试了不同的事情,但我不能实现捕捉/替换/忽略错误,并在点击按钮后继续。
在代码示例中,我希望有以下输出

we're in the else case
we're in the else case
replaced Error
we're in the else case
...

而是在replaced error之后得到finished,并且不发出任何事件。

Edit给定一个带有AnyPublisher<String, Error>的发布者,如何在发生错误时不完成将其转换为AnyPublisher<String, Never>,即忽略原始发布者发出的错误?

fdx2calv

fdx2calv1#

有提到一部WWDC电影,相信是2019年的《在实践中合并》,6点24分左右开始看:https://developer.apple.com/wwdc19/721
是的,.catch()终止上游发布者(电影7:45),并将其替换为.catch参数中的给定发布者,因此当使用Just()作为替换发布者时,通常会导致.finished被交付。
如果原来的发布者在失败后继续工作,则需要一个涉及.flatMap()的构造(movie 9:34)。导致可能失败的操作符需要在.flatMap内执行,如果需要,可以在那里处理。

.flatMap { data in
    return Just(data).decode(...).catch { Just(replacement) }
}

代替

.catch { return Just(replacement) } // DOES STOP UPSTREAM PUBLISHER

.flatMap中,你总是替换发行商,因此不关心替换发行商是否被.catch终止,因为它已经是一个替换发行商,我们原来的上游发行商是安全的。
这也是您的问题**Edit:**的答案,关于如何将<Output, Error>转换为<Output, Never>,因为.flatMap不输出任何错误,它的Never before and after the flatMap.所有与错误相关的步骤都封装在flatMap中。(提示检查Failure=Never:如果您为.assign(to:)获得了Xcode自动完成,那么我相信您有一个Failure=Never流,否则订阅者将不可用。

PlaygroundSupport.PlaygroundPage.current.needsIndefiniteExecution = true

enum MyError: Error {
    case someError
}
let cancellable = Timer.publish(every: 1, on: .main, in: .default)
    .autoconnect()
    .flatMap({ (input) in
        Just(input)
            .tryMap({ (input) -> String in
                if Bool.random() {
                    throw MyError.someError
                } else {
                    return "we're in the else case"
                }
            })
            .catch { (error) in
                Just("replaced error")
        }
    })
    .sink(receiveCompletion: { (completion) in
        print(completion)
        PlaygroundSupport.PlaygroundPage.current.finishExecution()
    }) { (output) in
        print(output)
}
ycl3bljg

ycl3bljg2#

我相信E.Coms的答案是正确的,但我会说得简单得多,处理错误而不导致管道在错误发生后停止处理值的关键是将错误处理发布器嵌套在flatMap中:

import UIKit
import Combine

enum MyError: Error {
  case someError
}

let cancel = [1,2,3]
  .publisher
  .flatMap { value in
    Just(value)
      .tryMap { value throws -> Int in
        if value == 2 { throw MyError.someError }
        return value
    }
    .replaceError(with: 666)
  }
  .sink(receiveCompletion: { (completed) in
    print(completed)
  }, receiveValue: { (sadf) in
    print(sadf)
  })

输出:

1
666
3
finished

您可以在操场上运行此示例。
关于OP的编辑:

Edit给定一个带有AnyPublisher<String, Error>的发布者,如何在发生错误时不完成将其转换为AnyPublisher<String, Never>,例如忽略原始发布者发出的错误?

你不能。

mzmfm0qo

mzmfm0qo3#

为此,可以使用catch运算符和Empty发布器:

let stringErrorPublisher = Just("Hello")
    .setFailureType(to: Error.self)
    .eraseToAnyPublisher() // AnyPublisher<String, Error>

let stringPublisher = stringErrorPublisher
    .catch { _ in Empty<String, Never>() }
    .eraseToAnyPublisher() // AnyPublisher<String, Never>
ldxq2e6h

ldxq2e6h4#

只要插入flatMap如下,你可以实现你想要的

self.myButton.publisher(for: \.touchUpInside).flatMap{
            (data: Bool) in
        return Just(data).tryMap({ _ -> String in
        if Bool.random() {
            throw MyError.someError
        } else {
            return "we're in the else case"
        }}).replaceError(with: "replaced Error")
    }.sink(receiveCompletion: { (completed) in
            print(completed)
        }, receiveValue: { (sadf) in
            print(sadf)
       }).store(in: &cancellables)

工作模型如下:

Just(parameter).
 flatMap{ (value)->AnyPublisher<String, Never> in 
 return MyPublisher(value).catch { <String, Never>() } 
 }.sink(....)

如果我们使用上面的例子,它可能是这样的:

let firstPublisher    = {(value: Int) -> AnyPublisher<String, Error> in
           Just(value).tryMap({ _ -> String in
           if Bool.random() {
               throw MyError.someError
           } else {
               return "we're in the else case"
            }}).eraseToAnyPublisher()
    }

    Just(1).flatMap{ (value: Int) in
        return  firstPublisher(value).replaceError(with: "replaced Error")
   }.sink(receiveCompletion: { (completed) in
            print(completed)
        }, receiveValue: { (sadf) in
            print(sadf)
       }).store(in: &cancellables)

在这里,您可以将firstPublisher替换为带一个参数的AnyPublisher。
同样在这里,firstPublisher只有一个值,它只能产生一个值。但是如果你的发布者可以产生多个值,它将在所有值都发出之前完成。

q5iwbnjs

q5iwbnjs5#

我也在为这个问题而挣扎,最后我找到了一个解决方案。需要理解的一点是,您无法从一个已完成的通量中恢复。解决方案是返回一个Result而不是Error

let button = UIButton()
button.publisher(for: .touchUpInside)
    .map({ control -> Result<String, Error> in
        if Bool.random() {
            return .failure(MyError.someError)
        } else {
            return .success("we're in the else case")
        }
    }).sink (receiveValue: { (result) in
        switch(result) {
        case .success(let value):
            print("Received value: \(value)")
        case .failure(let error):
            print("Failure: \(String(describing: error))")
        }
    })

对于阅读此线程并尝试编译代码的其他人,您需要从article导入代码(对于button.publisher(for: .touchUpIsinde)部分)。
另外,下面的代码可以处理PassthroughSubject的错误,并且永远不会完成通量:

let subscriber = PassthroughSubject<Result<String, MyError>, Never>()

subscriber
    .sink(receiveValue: { result in
        switch result {
        case .success(let value):
            print("Received value: \(value)")
        case .failure(let error):
            print("Failure: \(String(describing: error))")
        }
    })

不能直接使用PassthroughSubject<String, MyError>(),否则,通量将在错误发生时完成。

p1tboqfb

p1tboqfb6#

我在Peter Friese的这篇文章中找到了不完成管道并优雅地处理错误的最佳方法:https://peterfriese.dev/posts/swiftui-combine-networking-errorhandling/,方法是使用此Publisher扩展方法将接收到的内容Map到Result:

extension Publisher {
  func asResult() -> AnyPublisher<Result<Output, Failure>, Never> {
    self
      .map(Result.success)
      .catch { error in
        Just(.failure(error))
      }
      .eraseToAnyPublisher()
  }
}

然后你可以这样使用它:

publisher.flatMap {term in
            somePublisherWhichGivesError
                .asResult()
        }
        .sink(receiveValue: { value in
            switch value {
            case let .failure(error): self.handleError(error)
            case let .success(data): self.handleSuccess(data)
            }
        }).store(in: bag)
qqrboqgw

qqrboqgw7#

我建议将Publishertypealias Failure = Never一起使用,并将输出作为可选结果:typealias Output = Result<YourSuccessType, YourFailtureType>

unftdfkk

unftdfkk8#

发布服务器一直发射,直到完成或失败(出现错误),之后流将终止。
解决此问题的一种方法是将结果用作发布者类型

出版商

protocol SerivceProtocol {
    var value: Published<Result<Double, MyError>>.Publisher { get }
}

订户

service.value
        .sink { [weak self] in
            switch $0 {
            case let .success(value): self?.receiveServiceValue(value)
            case let .failure(error): self?.receiveServiceError(error)
            }
        }
        .store(in: &subscriptions)

相关问题