使用 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()