合并将一个 Publisher 变成另一个 Publisher
Combine turn one Publisher into another
我使用 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)
Option-click on p0
查看其推导类型。它是 AnyPublisher<URLRequest, OAuthError>
,因为那是 makeAuthenticatedRequest(request:)
被声明为 return。
现在添加:
let p1 = p0.tryMap(URLSession.shared.dataTaskPublisher(for:))
Option-click 在 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: [=13=])
.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
想要的输入。但是 Combine 认为您的第二个 tryMap
的输入应该是 URLSession.DataTaskPublisher
.
当您看到这种嵌套,发布者发布发布者时,这意味着您可能需要使用 flatMap
而不是 map
(或 tryMap
)。让我们这样做:
let p1 = p0.flatMap {
// ^^^^^^^ flatMap instead of tryMap
URLSession.shared.dataTaskPublisher(for: [=15=])
.eraseToAnyPublisher() }
.eraseToAnyPublisher()
现在我们得到一个 compile-time 错误:
Instance method 'flatMap(maxPublishers:_:)' requires the types 'OAuthError' and 'URLSession.DataTaskPublisher.Failure' (aka 'URLError') be equivalent
问题在于 Combine 无法展平嵌套,因为外部发布者的失败类型是 OAuthError
而内部发布者的失败类型是 URLError
。如果它们具有相同的故障类型,Combine 只能将它们展平。我们可以通过将两种故障类型转换为通用 Error
类型来解决此问题:
let p1 = p0
.mapError { [=16=] as Error }
.flatMap {
URLSession.shared.dataTaskPublisher(for: [=16=])
.mapError { [=16=] as Error }
.eraseToAnyPublisher() }
.eraseToAnyPublisher()
这样编译,Xcode告诉我们推导的类型是AnyPublisher<URLSession.DataTaskPublisher.Output, Error>
,这就是我们想要的。我们可以添加您的下一个 tryMap
,但我们只使用 map
,因为正文不会抛出任何错误:
let p2 = p1.map { [=17=].data }.eraseToAnyPublisher()
Xcode 告诉我们 p2
是一个 AnyPublisher<Data, Error>
,因此我们可以链接一个 decode
修饰符。
现在我们理清了类型,我们可以去掉所有的类型橡皮擦,把它们放在一起:
OAuthSession.current.makeAuthenticatedRequest(request: request)
.mapError { [=18=] as Error }
.flatMap {
URLSession.shared.dataTaskPublisher(for: [=18=])
.mapError { [=18=] as Error } }
.map { [=18=].data }
.decode(type: A.self, decoder: decoder)
我使用 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)
Option-click on p0
查看其推导类型。它是 AnyPublisher<URLRequest, OAuthError>
,因为那是 makeAuthenticatedRequest(request:)
被声明为 return。
现在添加:
let p1 = p0.tryMap(URLSession.shared.dataTaskPublisher(for:))
Option-click 在 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: [=13=])
.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
想要的输入。但是 Combine 认为您的第二个 tryMap
的输入应该是 URLSession.DataTaskPublisher
.
当您看到这种嵌套,发布者发布发布者时,这意味着您可能需要使用 flatMap
而不是 map
(或 tryMap
)。让我们这样做:
let p1 = p0.flatMap {
// ^^^^^^^ flatMap instead of tryMap
URLSession.shared.dataTaskPublisher(for: [=15=])
.eraseToAnyPublisher() }
.eraseToAnyPublisher()
现在我们得到一个 compile-time 错误:
Instance method 'flatMap(maxPublishers:_:)' requires the types 'OAuthError' and 'URLSession.DataTaskPublisher.Failure' (aka 'URLError') be equivalent
问题在于 Combine 无法展平嵌套,因为外部发布者的失败类型是 OAuthError
而内部发布者的失败类型是 URLError
。如果它们具有相同的故障类型,Combine 只能将它们展平。我们可以通过将两种故障类型转换为通用 Error
类型来解决此问题:
let p1 = p0
.mapError { [=16=] as Error }
.flatMap {
URLSession.shared.dataTaskPublisher(for: [=16=])
.mapError { [=16=] as Error }
.eraseToAnyPublisher() }
.eraseToAnyPublisher()
这样编译,Xcode告诉我们推导的类型是AnyPublisher<URLSession.DataTaskPublisher.Output, Error>
,这就是我们想要的。我们可以添加您的下一个 tryMap
,但我们只使用 map
,因为正文不会抛出任何错误:
let p2 = p1.map { [=17=].data }.eraseToAnyPublisher()
Xcode 告诉我们 p2
是一个 AnyPublisher<Data, Error>
,因此我们可以链接一个 decode
修饰符。
现在我们理清了类型,我们可以去掉所有的类型橡皮擦,把它们放在一起:
OAuthSession.current.makeAuthenticatedRequest(request: request)
.mapError { [=18=] as Error }
.flatMap {
URLSession.shared.dataTaskPublisher(for: [=18=])
.mapError { [=18=] as Error } }
.map { [=18=].data }
.decode(type: A.self, decoder: decoder)