组合将一个发布者转换为另一个发布者



我使用OAuth框架,该框架异步创建经过身份验证的请求,如下所示:

OAuthSession.current.makeAuthenticatedRequest(request: myURLRequest) { (result: Result<URLRequest, OAuthError>) in
switch result {
case .success(let request):
URLSession.shared.dataTask(with: request) { (data, response, error) in
// ...
}
// ...
}
}

我正在尝试让我的 OAuth 框架使用 Combine,所以我知道有一个makeAuthenticatedRequest方法的发布者版本,即:

public func makeAuthenticatedRequest(request: URLRequest) -> AnyPublisher<URLRequest, OAuthError>

我正在尝试使用它来替换上面的呼叫站点,如下所示:

OAuthSession.current.makeAuthenticatedRequestPublisher(request)
.tryMap(URLSession.shared.dataTaskPublisher(for:))
.tryMap { (data, _) in data } // Problem is here
.decode(type: A.self, decoder: decoder)

如上所述,问题在于将发布者的结果转换为新的发布者。我该怎么做呢?

你需要在dataTaskPublisher(for:)周围使用flatMap,而不是tryMap

看看类型。从这个开始:

let p0 = OAuthSession.current.makeAuthenticatedRequest(request: request)

按住选项单击p0以查看其推断类型。这是AnyPublisher<URLRequest, OAuthError>,因为这是makeAuthenticatedRequest(request:)被宣布返回的内容。

现在添加这个:

let p1 = p0.tryMap(URLSession.shared.dataTaskPublisher(for:))

按住选项单击p1以查看其推断类型Publishers.TryMap<AnyPublisher<URLRequest, OAuthError>, URLSession.DataTaskPublisher>。哎呀,这有点难以理解。通过使用eraseToAnyPublisher来简化它:

let p1 = p0
.tryMap(URLSession.shared.dataTaskPublisher(for:))
.eraseToAnyPublisher()

现在推断出的p1类型是AnyPublisher<URLSession.DataTaskPublisher, Error>.它仍然有一些神秘的类型URLSession.DataTaskPublisher,所以让我们也删除它:

let p1 = p0.tryMap {
URLSession.shared.dataTaskPublisher(for: $0)
.eraseToAnyPublisher() }
.eraseToAnyPublisher()

现在 Xcode 可以告诉我们,推断出的p1类型是AnyPublisher<AnyPublisher<URLSession.DataTaskPublisher.Output, URLSession.DataTaskPublisher.Failure>, OAuthError>.让我重新格式化以提高可读性:

AnyPublisher<
AnyPublisher<
URLSession.DataTaskPublisher.Output, 
URLSession.DataTaskPublisher.Failure>,
OAuthError>

这是一个发布发布URLSession.DataTaskPublisher.Output的发布者。

这不是你所期望的,这就是你的第二个tryMap失败的原因。您认为您正在创建URLSession.DataTaskPublisher.Output的发布者(这是元组(data: Data, response: URLResponse)typealias),这就是您的第二个tryMap想要的输入。但Combing认为你的第二个tryMap的输入应该是一个URLSession.DataTaskPublisher

当您看到这种嵌套时,发布发布者的发布者,这意味着您可能需要使用flatMap而不是map(或tryMap)。让我们这样做:

let p1 = p0.flatMap {
//   ^^^^^^^ flatMap instead of tryMap
URLSession.shared.dataTaskPublisher(for: $0)
.eraseToAnyPublisher() }
.eraseToAnyPublisher()

现在我们得到一个编译时错误:

🛑 实例方法 'flatMap(maxPublishers:_:)' 要求类型"OAuthError" 和 'URLSession.DataTaskPublisher.Failure' (aka 'URLError') 是等效

问题是 Combine 无法平展嵌套,因为外部发布者的故障类型是OAuthError,内部发布者的故障类型是URLError。仅当它们具有相同的故障类型时,组合才能展平它们。我们可以通过将两种故障类型转换为通用Error类型来解决此问题:

let p1 = p0
.mapError { $0 as Error }
.flatMap {
URLSession.shared.dataTaskPublisher(for: $0)
.mapError { $0 as Error }
.eraseToAnyPublisher() }
.eraseToAnyPublisher()

这样编译,Xcode 告诉我们推导的类型是AnyPublisher<URLSession.DataTaskPublisher.Output, Error>,这就是我们想要的。我们可以附加您的下一个tryMap,但让我们只使用map,因为正文不会抛出任何错误:

let p2 = p1.map { $0.data }.eraseToAnyPublisher()

Xcode 告诉我们p2是一个AnyPublisher<Data, Error>,所以我们可以链接一个decode修饰符。

现在我们已经理顺了类型,我们可以摆脱所有类型橡皮擦并将它们放在一起:

OAuthSession.current.makeAuthenticatedRequest(request: request)
.mapError { $0 as Error }
.flatMap {
URLSession.shared.dataTaskPublisher(for: $0)
.mapError { $0 as Error } }
.map { $0.data }
.decode(type: A.self, decoder: decoder)

最新更新