I am trying to decode the downloaded JSON into a structure with the following code.
static func request(url: URL) -> AnyPublisher<SomeDecodableStruct, Error> {
return URLSession.shared.dataTaskPublisher(for: url)
.map { $0.data }
.decode(type: SomeDecodableStruct.self, decoder: JSONDecoder())
.eraseToAnyPublisher()
}
However, if processing fails, I would like you to return information on whether the request processing failed or the decoding processing failed.
Therefore, I defined the FailureReason
enum that conforms to the Error
protocol as follows.
enum FailureReason : Error {
case sessionFailed(error: URLError)
case decodingFailed
}
static func request(url: URL) -> AnyPublisher<SomeDecodableStruct, FailureReason> {
// ???
}
How do I define a request(url:)
that satisfies this FailureReason
?
Error-handling techniques for logic errors or bugs is usually by meticulous application debugging or troubleshooting. Error-handling applications can resolve runtime errors or have their impact minimized by adopting reasonable countermeasures depending on the environment.
There are four ways to handle errors in Swift. You can propagate the error from a function to the code that calls that function, handle the error using a do - catch statement, handle the error as an optional value, or assert that the error will not occur.
What's a sink? While a complete explanation of Combine, publishers, subscribers, and sinks is beyond the scope of this article, for our purposes here it's probably enough to know that in Combine a sink is the code receives data and completion events or errors from a publisher and deals with them.
Combine is strongly typed with respect to errors, so you must transform your errors to the correct type using mapError
or be sloppy like RxSwift and decay everything to 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()
}
}
In this situation I wouldn't declare the publisher with other Failure
type than Never
. Otherwise the Publisher will send a completion with first error it encounters and stop publishing altogether. It is much better to make the Output
of type Result
. After each step which can produce an error you map it to your Error type using .mapError
and as the last thing catch the error and return 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()
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With