Как я могу расшифровать пользовательскую ошибку с помощью Combine?

#ios #swift #nsurlsession #combine #urlsession

#iOS #swift #nsurlsession #объединить #urlsession

Вопрос:

API, к которому я обращаюсь, может возвращать JSON, содержащий сообщение об ошибке.

Как я могу сказать Combine , чтобы попытаться декодировать эту пользовательскую ошибку, если я ожидаю, что другой Decodable объект будет возвращен при успешном запросе?

В настоящее время мой код выглядит следующим образом:

     private var cancellable: AnyCancellable?

    internal func perform<T>(request: URLRequest, completion: @escaping (Result<T, Error>) -> Void) where T: Decodable {
            cancellable = session.dataTaskPublisher(for: request)
                .tryMap { output in
                    guard let response = output.response as? HTTPURLResponse, response.statusCode == 200 else {
                        throw HTTPError.statusCode
                    }
                    return output.data
                }
                .decode(type: T.self, decoder: JSONDecoder())
                .eraseToAnyPublisher()
                .sink(receiveCompletion: { _completion in
                    guard case .failure(let error) = _completion else {
                        return
                    }
                    completion(.failure(error))
                }, receiveValue: { value in
                    completion(.success(value))
                })
}
 

С URLSession помощью я бы сделал что-то вроде этого:

 URLSession.shared.dataTask(with: request) { data, response, error in
    // Check for any connection errors
    if let error = error {
        completion(.failure(error))
        return
    }
    // Read data
    guard let data = data, !data.isEmpty else {
        completion(.failure(SPTError.noDataReceivedError))
        return
    }
    // Check response's status code, if it's anything other than 200 (OK), try to decode SPTError from the data.
    guard let httpResponse = response as? HTTPURLResponse,
          httpResponse.statusCode == 200 else {
        let sptError = (try? JSONDecoder().decode(SPTError.self, from: data)) ?? SPTError.badRequest
        completion(.failure(sptError))
        return
    }
    // Decode requested objects
    do {
        let object = try JSONDecoder().decode(T.self, from: data)
        completion(.success(object))
    } catch {
        print(completion(.failure(error)))
    }
}.resume()
 

SPTError это просто структура, содержащая код и сообщение, она соответствует Codable

Комментарии:

1. И в чем проблема с вашим текущим кодом?

2. Он только пытается декодировать T объект, если ответ отличается от 200 кода, он выдает общую ошибку, и я хотел бы декодировать данные как SPTError

Ответ №1:

Когда у вас есть условное ветвление, вы можете использовать .flatMap , чтобы определить, какого издателя возвращать, на основе любых условий, которые вы проверяете.

FlatMap должен соответствовать типу сбоя восходящего потока и возвращенного издателя, поэтому .mapError сначала нужно перейти к общему Error . И поскольку другая ветвь — это другая цепочка издателей, введите «стереть их все», чтобы AnyPublisher :

 URLSession.shared.dataTaskPublisher(for: url)
   .mapError { $0 as Error }
   .flatMap() { output -> AnyPublisher<T, Error> in
      if output.data.isEmpty {
         return Fail(error: SPTError.noDataReceivedError).eraseToAnyPublisher()
      }
        
      guard let httpResponse = output.response as? HTTPURLResponse else {
         return Fail(error: HTTPError.statusCode).eraseToAnyPublisher()
      }
        
      if httpResponse.statusCode == 200 {
         return Just(output.data)
            .decode(type: T.self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
      } else {
         return Just(output.data)
            .decode(type: SPTError.self, decoder: JSONDecoder())
            .flatMap { Fail(error: $0) }
            .eraseToAnyPublisher()
       }
   }
   .eraseToAnyPublisher()