Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Swift Lille Meetup #2 - @CallMeSH - How to use the result type to handle errors in Swift 5 ?

Swift Lille Meetup #2 - @CallMeSH - How to use the result type to handle errors in Swift 5 ?

Swift Lille

April 09, 2019
Tweet

More Decks by Swift Lille

Other Decks in Technology

Transcript

  1. A function throws func divide(_ divident: Float, _ divider: Float)

    throws -> Float { if divider == 0 { throw DivisionError.illegalDivider } return divident / divider }
  2. The call must be tried — do try catch —

    try? — try! ! do { let result = try divide(50.0, 0.0) print("The result is: \(result)") } catch let error { print("An error happened: \(error.localizedDescription)") }
  3. The Swift error handling model — Pros: — Easy to

    use — Helped by the compiler — Cons: — The Error is not type safe — How do you handle asynchronous errors ?
  4. Can an asynchronous function throw? func fetchProducts(handler: @escaping (() throws

    -> [Product]) -> Void) { let request = URLRequest(url: URL(string:"https://mystore.com/products")!) URLSession.shared.dataTask(with: request) { (data, response, error) in if let error = error { handler { throw error } } if let data = data { handler { return try decoder.decode([Product].self, from: data) } } } } fetchProducts { (getResult) in do { let products = try getResult() print("Here is my list of products:") print(products) } catch let error { print("An error happened: \(error.localizedDescription)") } }
  5. Can an asynchronous function throw? — They can, but that

    makes the code complicated... — Still not type safe...
  6. They come in all shapes and forms... enum Result<Type, ErrorType>

    { case success(Type) case failure(ErrorType) }
  7. How do you use it? func fetchProducts(handler: @escaping (Result<[Product], FetchProductError>)

    -> Void ) { let request = URLRequest(url: URL(string:"https://mystore.com/products")!) URLSession.shared.dataTask(with: request) { (data, response, error) in if let error = error { handler(.failure(.networkError(error))) } if let data = data { do { let products = try decoder.decode([Product].self, from: data) handler(.success(products)) } catch let error { handler(.failure(.parsingError(error))) } } } }
  8. fetchProducts { (result) in switch result { case let .success(products):

    print("The products are:") print(products) case .failure(.networkError): print("A network error happened!") case .failure(.parsingError): print("A parsing error happened!") } }
  9. Result Type — Pros: — Removes the ambiguity of (data:

    Data?, error: Error?) — The error type is safe — Easier than the async throwing — Cons: — How do you bridge between implementations?
  10. A few functional highlights // Returns a new result, mapping

    any success value using the given transformation. func map<NewSuccess>((Success) -> NewSuccess) -> Result<NewSuccess, Failure> // Returns a new result, mapping any failure value using the given transformation. func mapError<NewFailure>((Failure) -> NewFailure) -> Result<Success, NewFailure> // Returns a new result, mapping any success value using the given transformation and unwrapping the produced result. func flatMap<NewSuccess>((Success) -> Result<NewSuccess, Failure>) -> Result<NewSuccess, Failure> // Returns a new result, mapping any failure value using the given transformation and unwrapping the produced result. func flatMapError<NewFailure>((Failure) -> Result<Success, NewFailure>) -> Result<Success, NewFailure>
  11. This allows chaining func fetchProducts(handler: @escaping (Result<Data, FetchProductError>) -> Void

    ) { let request = URLRequest(url: URL(string:"https://mystore.com/products")!) URLSession.shared.dataTask(with: request) { (data, response, error) in if let error = error { handler(.failure(.networkError(error))) } if let data = data { handler(.success(data)) } } } fetchProducts { (result) in let data = result print(data) }
  12. This allows chaining func parseProducts(_ data: Data) -> Result<[Product], FetchProductError>

    { do { let products = try decoder.decode([Product].self, from: data) return .success(products) } catch let error { return .failure(.parsingError(error)) } } func getFirst<T>(_ data: [T]) -> T? { return data.first }
  13. This allows chaining fetchProducts { (result) in let firstProduct =

    result .flatMap(parseProducts) .map(getFirst) print(firstProduct) }
  14. You can also bridge from the standard error model func

    parseProducts(_ data: Data) -> Result<[Product], FetchProductError> { do { let products = try decoder.decode([Product].self, from: data) return .success(products) } catch let error { return .failure(.parsingError(error)) } }
  15. You can also bridge from the standard error model func

    parseProducts(_ data: Data) -> Result<[Product], FetchProductError> { return Result { try decoder.decode([Product].self, from: data)} .mapError { FetchProductError.parsingError($0) } }
  16. It depends... — You are using libraries? You'll have to

    wait for them — Alamofire is almost ready — So is PromiseKit — ... — Not using livraries? — Install Xcode 10.2, you're good to go!
  17. Result is the step 1 — Expect Foundation to adapt

    soon — Swift 6 or 7 will bring Coroutines (async/await)