合并发布者时如何处理错误?

Ori*_*iol 1 ios swift swiftui combine

我最近开始使用组合,我正在尝试创建一个返回多个数据源组合的存储库。为此,每个数据源都通过一个非常简单的加载方法加载自己的数据:

func loadData() -> AnyPublisher<DataObject, Error>
Run Code Online (Sandbox Code Playgroud)

要合并我想到使用的数据源combineLatest,因为它将等待数据源完成加载,然后它会发布包含数据的组合集或指示失败的错误:

func loadData() -> AnyPublisher<[DataObject], Error> {
   return dataSource1.loadData()
             .combineLatest(dataSource2.loadData())
             .map { $0.0 + $0.1 }
             .eraseToAnyPublisher()
}
Run Code Online (Sandbox Code Playgroud)

总体而言,它的行为似乎没问题,我可以调用repository.loadData()并获得一个包含这两个项目的数据的数组。但是,如果任何数据源发生故障,情况就不是这样了。在这种情况下,无论其他数据源是否成功,load 方法都会返回错误。

合并发布者时是否有标准或推荐的方法来收集所有错误?在我的使用上下文中,我希望仅当两个发布者都失败时才能够丢弃错误,但如果只有其中一个发布者失败,则继续执行并成功。

rob*_*off 6

你是说你想要这个:

  1. 如果dataSource1失败并dataSource2产生输出,则丢弃 的失败dataSource1并仅传递 的输出dataSource2

  2. 如果dataSource1产生输出并dataSource2失败,则仅传递 的输出dataSource1并丢弃 的失败dataSource2

  3. 如果 和dataSource1dataSource2产生输出,则传递组合输出。

  4. 如果 和dataSource1dataSource2失败,则传递错误之一。

我假设每个数据源最多产生一个输出。这是一个测试设置:

typealias DataObject = String

struct DataSource {
    var result: Result<DataObject, Error>

    func loadData() -> AnyPublisher<DataObject, Error> {
        return result.publisher.eraseToAnyPublisher()
    }
}
Run Code Online (Sandbox Code Playgroud)

我们确实想使用combineLatest,但我们不能让任何一个输入combineLatest失败,因为这会导致combineLatest失败。我们只想在两个数据源都失败的combineLatest情况下失败。因此,我们需要一种方法将错误作为其输入发布者之一的输出传递,而不是作为其输入发布者之一的失败。combineLatest

我们通过将每个输入发布者转换为具有OutputofResult<DataObject, Error>和 a Failureof来实现这一点Never

func combine(
    _ source1: DataSource,
    _ source2: DataSource
) -> AnyPublisher<[DataObject], Error> {
    let ds1 = source1.loadData()
        .map { Result.success($0) }
        .catch { Just(Result.failure($0)) }

    let ds2 = source2.loadData()
        .map { Result.success($0) }
        .catch { Just(Result.failure($0)) }

    let combo = ds1.combineLatest(ds2)
        .tryMap { r1, r2 -> [DataObject] in
            switch (r1, r2) {
            case (.success(let s1), .success(let s2)): return [s1, s2]
            case (.success(let s1), .failure(_)): return [s1]
            case (.failure(_), .success(let s2)): return [s2]
            case (.failure(let f1), .failure(_)): throw f1
            }
        }

    return combo.eraseToAnyPublisher()
}
Run Code Online (Sandbox Code Playgroud)

我们来测试一下:

struct MockError: Error { }

combine(.init(result: .success("hello")), .init(result: .success("world")))
    .sink(
        receiveCompletion: { print($0) },
        receiveValue: { print($0) })
// Output:
// ["hello", "world"]
// finished

combine(.init(result: .success("hello")), .init(result: .failure(MockError())))
    .sink(
        receiveCompletion: { print($0) },
        receiveValue: { print($0) })
// Output:
// ["hello"]
// finished

combine(.init(result: .failure(MockError())), .init(result: .success("world")))
    .sink(
        receiveCompletion: { print($0) },
        receiveValue: { print($0) })
// Output:
// ["world"]
// finished

combine(.init(result: .failure(MockError())), .init(result: .failure(MockError())))
    .sink(
        receiveCompletion: { print($0) },
        receiveValue: { print($0) })
// Output:
// failure(__lldb_expr_28.MockError())
Run Code Online (Sandbox Code Playgroud)