首页 > 解决方案 > 如何成功匹配两个发布者之间的失败映射(Never 和 URLError)

问题描述

在搜索了一些关于 Combine 的不同资源(包括 Joseph Heck 和 Donny Wals 的书)之后,我接近理解 DataTaskPublishers 的链接,但未能将它们连接到一系列链接运算符中。我似乎对第一个发布者的输出之间的错误与第二个发布者的预期输入不匹配这一事实感到困惑。两个 Publisher 扩展在未连接时都可以工作,所以我确信它缺乏加入两者的能力。我原以为 mapError() 会起作用,但它不想编译。

这是设置:

给定两个自定义发布者:

extension Publisher where Output == MKCoordinateRegion, Failure == URLError {

func toRegionDataTask() -> AnyPublisher<URLSession.DataTaskPublisher.Output, URLError> {
    return self
        .flatMap({ region -> URLSession.DataTaskPublisher in
                  ...
                  ... 
                  ...
                  return URLSession.shared.dataTaskPublisher(for: request)       
                  })
        .eraseToAnyPublisher()
    }
}

extension Publisher where Output == [String], Failure == Never {

func toGeographiesDataTask() ->  AnyPublisher<URLSession.DataTaskPublisher.Output, URLError {
    return self
        .setFailureType(to: URLError.self)
        .flatMap({ ids -> URLSession.DataTaskPublisher in
                   ...
                   ...
                   ...
                  return URLSession.shared.dataTaskPublisher(for: request)
                 })
         .eraseToAnyPublisher()
}

}

然后我有一个函数试图将两者链接在一起,如下所示:

   let passthroughSubj = PassthroughSubject<MKCoordinateRegion,URLError>()

    passthroughSubj
    .toRegionDataTask()                                         // returns <DataTaskPublisher, URLError>
    .map { $0.data }                                            // returns <FlatMap, ?>
    .decode(type: ApiResponse.self, decoder:JSONDecoder())      // returns <ApiResonse, ?>
    .map {$0.body.data(using: .utf8)! }                         // returns <Data, ?>
    .decode(type: AmznResponse.self, decoder: JSONDecoder())    // returns <AmznResponse, ?>
    .map ({ response -> [AmznItem] in                           //
                return response.contents                        // returns <[AmznItem], ?>
    })
    .map ({ items -> [String] in                                // returns <[String], Never> ?
            var ids = [String]()
            for item in items {
                    ids.append(item.geoid)
            }
            return ids
            })
//
//        .toGeographiesDataTask()                                  // get error "Referencing instance method
//        .map { $0.data }                                          // 'toGeographiesDataTask()' on 'Publisher'
//        .decode(type: ApiResponse.self, decoder:JSONDecoder())    // requires the types 'Error' and 'Never'
//        .map {$0.body.data(using: .utf8)! }                       // be equivalent"
//        .decode(type: AmznResponse.self, decoder: JSONDecoder())
//        .map { $0.contents }
//
    .sink(receiveCompletion: { (completion) in
        switch completion {
        case .failure(let error):
            print(error)
        case .finished:
            print("DONE")
        }
        }, receiveValue: { data in
           print(data)
        })
    .store(in: &cancellables)

passthroughSubj.send(region1)

如果我取消注释第二个自定义发布者,我会收到右侧显示的错误消息。我的理解是 .map 正在返回 <[String],Never> 但最终因为 DataTaskPublisher 可能失败,我需要将其映射到 URLError 。但是 .mapError 的组合似乎也无法编译。

我在这里错过了一些基本的东西吗?似乎是一个容易解决的问题,但我没有发现任何突出的问题。

我已经看到了使用 .flatMap 将它们链接在一起的示例,但是由于我将一个的输出转换为第二个自定义发布者的输入,这似乎是不可能的。

非常欢迎任何帮助或指示!谢谢。

标签: combine

解决方案


map运算符只转换,OutputError保持原样。因此,如果我要填写你OutputFailure对的空白,我最终会得到这个:

// returns <DataTaskPublisher, URLError>
// returns <Data, URLError>
// returns <ApiResonse, Error> (decode replaces the Failure with Error)
// returns <Data, Error>
// returns <AmznResponse, Error>
// returns <[AmznItem], Error>
// returns <[String], Error>

您的实现toGeographiesDataTask要求它所应用的发布者具有Never错误,这就是您得到编译器错误的原因。

我认为您可以从扩展中删除错误要求并使其成为

extension Publisher where Output == [String] {
  // implementation
}

然后toGeographiesDataTask()你可以使用以下方法替换URLError数据任务发出的内容mapError

func toGeographiesDataTask() ->  AnyPublisher<URLSession.DataTaskPublisher.Output, Error> {
    return self
        .flatMap({ ids -> AnyPublisher<URLSession.DataTaskPublisher.Output, Error> in
                   ...
                   ...
                   ...
                  return URLSession.shared.dataTaskPublisher(for: request)
                    .mapError({ $0 as Error})
                    .eraseToAnyPublisher()
                 })
         .eraseToAnyPublisher()
}

我认为这应该让链条的其余部分也能工作,你应该最终<[AmznItem], Error>成为<Output, Failure>链条的末端。

虽然我还没有在操场上尝试过,但我很确定这会让你继续前进。


推荐阅读