Как лучше всего обрабатывать ошибки в Combine? - PullRequest
0 голосов
/ 05 октября 2019

Я пытаюсь декодировать загруженный JSON в структуру со следующим кодом.

static func request(url: URL) -> AnyPublisher<SomeDecodableStruct, Error> {
    return URLSession.shared.dataTaskPublisher(for: url)
        .map { $0.data }
        .decode(type: SomeDecodableStruct.self, decoder: JSONDecoder())
        .eraseToAnyPublisher()
}

Однако, если обработка не удалась, я хотел бы, чтобы вы вернули информацию о том, была ли неудача обработки запроса или декодированиеобработка не удалась. Поэтому я определил перечисление FailureReason, соответствующее протоколу Error, следующим образом.

enum FailureReason : Error {
    case sessionFailed(error: URLError)
    case decodingFailed
}

static func request(url: URL) -> AnyPublisher<SomeDecodableStruct, FailureReason> {
    // ???
}

Как определить request(url:), который удовлетворяет этому FailureReason?

Ответы [ 2 ]

1 голос
/ 08 октября 2019

Комбинат строго типизирован по отношению к ошибкам, поэтому вы должны преобразовать свои ошибки в правильный тип, используя mapError, или быть неряшливым, как RxSwift, и уменьшить все до Error.

enum NetworkService {
  enum FailureReason : Error {
      case sessionFailed(error: URLError)
      case decodingFailed
      case other(Error)
  }

  static func request<SomeDecodable: Decodable>(url: URL) -> AnyPublisher<SomeDecodable, FailureReason> {
    return URLSession.shared.dataTaskPublisher(for: url)
      .map(\.data)
      .decode(type: SomeDecodable.self, decoder: JSONDecoder())
      .mapError({ error in
        switch error {
        case is Swift.DecodingError:
          return .decodingFailed
        case let urlError as URLError:
          return .sessionFailed(error: urlError)
        default:
          return .other(error)
        }
      })
      .eraseToAnyPublisher()
  }
}
0 голосов
/ 06 октября 2019

В этой ситуации я бы не объявил издателя с другим типом Failure, чем Never. В противном случае издатель отправит завершение с первой обнаруженной ошибкой и вообще прекратит публикацию. Гораздо лучше сделать Output типа Result. После каждого шага, который может вызвать ошибку, вы сопоставляете ее с типом ошибки, используя .mapError, и, как последнее, перехватываете ошибку и возвращает Result.failure

func request(url: URL) -> AnyPublisher<Result<SomeDecodableStruct, FailureReason>, Never> {
        return URLSession.shared.dataTaskPublisher(for: url)
                    .mapError { Error.sessionFailed(error: $0) }
                    .map { $0.data }
                    .decode(type: SomeDecodableStruct.self, decoder: JSONDecoder())
                    .map { Result<SomeDecodableStruct, FailureReason>.success($0)}
                    .mapError { _ in Error.decodingFailed }
                    .catch { Just<Result<SomeDecodableStruct, FailureReason>>(.failure($0)) }
                    .eraseToAnyPublisher()
    }
...