你需要使用flatMap
,而不是tryMap
,周围dataTaskPublisher(for:)
。
看类型。从这个开始:
let p0 = OAuthSession.current.makeAuthenticatedRequest(request: request)
Option-clickp0
以查看其推导类型。它是AnyPublisher<URLRequest, OAuthError>
,因为那makeAuthenticatedRequest(request:)
是声明返回的内容。
现在添加:
let p1 = p0.tryMap(URLSession.shared.dataTaskPublisher(for:))
Option-clickp1
以查看其推导类型,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
(这是typealias
tuple的一个(data: Data, response: URLResponse)
),这就是你第二个tryMap
想要的输入。但是 Combine 认为你第二个tryMap
的输入应该是一个URLSession.DataTaskPublisher
.
When you see this kind of nesting, with a publisher that publishes publishers, it means you probably needed to use flatMap
instead of map
(or tryMap
). Let's do that:
let p1 = p0.flatMap {
// ^^^^^^^ flatMap instead of tryMap
URLSession.shared.dataTaskPublisher(for: $0)
.eraseToAnyPublisher() }
.eraseToAnyPublisher()
Now we get a compile-time error:
Instance method 'flatMap(maxPublishers:_:)' requires the types 'OAuthError' and 'URLSession.DataTaskPublisher.Failure' (aka 'URLError') be equivalent
The problem is that Combine can't flatten the nesting because the outer publisher's failure type is OAuthError
and the inner publisher's failure type is URLError
. Combine can only flatten them if they have the same failure type. We can fix this problem by converting both failure types to the general Error
type:
let p1 = p0
.mapError { $0 as Error }
.flatMap {
URLSession.shared.dataTaskPublisher(for: $0)
.mapError { $0 as Error }
.eraseToAnyPublisher() }
.eraseToAnyPublisher()
This compiles, and Xcode tells us that the deduced type is AnyPublisher<URLSession.DataTaskPublisher.Output, Error>
, which is what we want. We can tack on your next tryMap
, but let's just use map
instead because the body can't throw any errors:
let p2 = p1.map { $0.data }.eraseToAnyPublisher()
Xcode tells us p2
is an AnyPublisher<Data, Error>
, so we could then chain a decode
modifier.
Now that we have straightened out the types, we can get rid of all the type erasers and put it all together:
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)