Использование операторов Combine для преобразования Future в Publisher - PullRequest
1 голос
/ 13 июля 2020

Я использую API (Firebase), который предоставляет асинхронный c интерфейс для большинства вызовов его методов. Для каждого запроса, который я делаю через свой собственный API, я хочу добавить токен пользователя в качестве заголовка, если такой токен существует. Я пытаюсь сделать весь процесс частью одного и того же конвейера в 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()
}

Я получаю сообщение об ошибке внутри оператора tryMap при попытке JSON расшифровать данные ответа:

Значение типа 'URLSession.DataTaskPublisher' не имеет элемента 'data'

Я все еще вертлюсь вокруг Combine, но могу Не понимаю, что я здесь делаю не так. Любая помощь будет принята с благодарностью!

1 Ответ

0 голосов
/ 13 июля 2020

Вы пытаетесь подключиться к другому издателю. В большинстве случаев это знак того, что вам нужен 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 { $0 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()
...