使用 Combine 运算符将 Future 转换为 Publisher
Using Combine operators to transform Future into Publisher
我正在使用一个 API (Firebase),它为大多数方法调用公开了一个异步接口。对于我通过自己的 API 发出的每个请求,如果存在这样的令牌,我想将用户令牌添加为 header。我正在尝试使整个过程成为 Combine 中同一管道的一部分。
我有以下代码:
struct Response<T> {
let value: T
let response: URLResponse
}
...
func makeRequest<T: Decodable>(_ req: URLRequest, _ decoder: JSONDecoder = JSONDecoder()) -> AnyPublisher<T, Error> {
var request = req
return Future<String?, Error> { promise in
if let currentUser = Auth.auth().currentUser {
currentUser.getIDToken() { (idToken, error) in
if error != nil {
promise(.failure(error!))
} else {
promise(.success(idToken))
}
}
} else {
promise(.success(nil))
}
}
.map { idToken -> URLSession.DataTaskPublisher in
if idToken != nil {
request.addValue("Bearer \(idToken!)", forHTTPHeaderField: "Authorization")
}
return URLSession.shared.dataTaskPublisher(for: request)
}
.tryMap { result -> Response<T> in
let value = try decoder.decode(T.self, from: result.data)
return Response(value: value, response: result.response)
}
.receive(on: DispatchQueue.main)
.map(\.value)
.eraseToAnyPublisher()
}
我在尝试 JSON 解码响应数据时在 tryMap 运算符中遇到错误:
Value of type 'URLSession.DataTaskPublisher' has no member 'data'
我仍然在思考 Combine,但不明白我做错了什么。任何帮助将不胜感激!
您正在尝试映射到另一个发布者。大多数时候,这表明您需要 flatMap
。如果您改用 map
,您将得到一个发布另一个发布者的发布者,这几乎肯定不是您想要的。
但是,flatMap
要求上游发布者(承诺)与您要映射到的发布者具有相同的故障类型。但是,在这种情况下它们并不相同,因此您需要在数据会话发布者上调用 mapError
以更改其错误类型:
return Future<String?, Error> { promise in
promise(.failure(NSError()))
}
// flatMap and notice the change in return type
.flatMap { idToken -> Publishers.MapError<URLSession.DataTaskPublisher, Error> in
if idToken != nil {
request.addValue("Bearer \(idToken!)", forHTTPHeaderField: "Authorization")
}
return URLSession.shared.dataTaskPublisher(for: request)
// change the error type
.mapError { [=10=] as Error } // "as Error" isn't technically needed. Just for clarity
}
.tryMap { result -> Response<T> in
let value = try decoder.decode(T.self, from: result.data)
return Response(value: value, response: result.response)
}
.receive(on: DispatchQueue.main)
.map(\.value)
.eraseToAnyPublisher()
我正在使用一个 API (Firebase),它为大多数方法调用公开了一个异步接口。对于我通过自己的 API 发出的每个请求,如果存在这样的令牌,我想将用户令牌添加为 header。我正在尝试使整个过程成为 Combine 中同一管道的一部分。
我有以下代码:
struct Response<T> {
let value: T
let response: URLResponse
}
...
func makeRequest<T: Decodable>(_ req: URLRequest, _ decoder: JSONDecoder = JSONDecoder()) -> AnyPublisher<T, Error> {
var request = req
return Future<String?, Error> { promise in
if let currentUser = Auth.auth().currentUser {
currentUser.getIDToken() { (idToken, error) in
if error != nil {
promise(.failure(error!))
} else {
promise(.success(idToken))
}
}
} else {
promise(.success(nil))
}
}
.map { idToken -> URLSession.DataTaskPublisher in
if idToken != nil {
request.addValue("Bearer \(idToken!)", forHTTPHeaderField: "Authorization")
}
return URLSession.shared.dataTaskPublisher(for: request)
}
.tryMap { result -> Response<T> in
let value = try decoder.decode(T.self, from: result.data)
return Response(value: value, response: result.response)
}
.receive(on: DispatchQueue.main)
.map(\.value)
.eraseToAnyPublisher()
}
我在尝试 JSON 解码响应数据时在 tryMap 运算符中遇到错误:
Value of type 'URLSession.DataTaskPublisher' has no member 'data'
我仍然在思考 Combine,但不明白我做错了什么。任何帮助将不胜感激!
您正在尝试映射到另一个发布者。大多数时候,这表明您需要 flatMap
。如果您改用 map
,您将得到一个发布另一个发布者的发布者,这几乎肯定不是您想要的。
但是,flatMap
要求上游发布者(承诺)与您要映射到的发布者具有相同的故障类型。但是,在这种情况下它们并不相同,因此您需要在数据会话发布者上调用 mapError
以更改其错误类型:
return Future<String?, Error> { promise in
promise(.failure(NSError()))
}
// flatMap and notice the change in return type
.flatMap { idToken -> Publishers.MapError<URLSession.DataTaskPublisher, Error> in
if idToken != nil {
request.addValue("Bearer \(idToken!)", forHTTPHeaderField: "Authorization")
}
return URLSession.shared.dataTaskPublisher(for: request)
// change the error type
.mapError { [=10=] as Error } // "as Error" isn't technically needed. Just for clarity
}
.tryMap { result -> Response<T> in
let value = try decoder.decode(T.self, from: result.data)
return Response(value: value, response: result.response)
}
.receive(on: DispatchQueue.main)
.map(\.value)
.eraseToAnyPublisher()