Using Result type with async/await

I'm pretty sure it is, but you'll have to try and actually build it.

I have played some time in playground, the code now looks like:

func doSomething() {    
  fetchProducts()
    .mapSuccess { products in
      fetchProductImages(products)
    } onFailureDoFinally: { error in
      handleError(error: error)
    }
    .mapSuccess { productImages in
      processProductImages(productImages)
    } onFailureDoFinally: { error in
      handleError(error: error)
    }
}
extension Result {  
  @discardableResult
  func mapSuccess<NewSuccess, NewFailure>(onSuccess: (Success) -> Result<NewSuccess, NewFailure>,
                                          onFailureDoFinally: (Failure) -> Void) -> Result<NewSuccess, NewFailure>? {
    switch self {
    case let .success(value):
      return onSuccess(value)
    case let .failure(error):
      onFailureDoFinally(error)
      return nil
    }
  }
}

extension Optional {
  @discardableResult
  func mapSuccess<Success, Failure, NewSuccess, NewFailure>(onSuccess: (Success) -> Result<NewSuccess, NewFailure>,
                                                            onFailureDoFinally: (Failure) -> Void)
  -> Result<NewSuccess, NewFailure>? where Wrapped == Result<Success, Failure> {
    switch self {
    case let .success(value):
      return onSuccess(value)
    case let .failure(error):
      onFailureDoFinally(error)
      return nil
    case .none:
      return nil
    }
  }
}

So, technically it can be done, but I can hardly say it is as good API:

  • it is easy to get a compiler error, the error explanation is hard to understand in long chains
  • it can (and will, I suppose) be incorrectly used and understood
  • control flow is weird

One more variant:

func doSomething() {    
  guard let products = fetchProducts().getValue(onErrorDo: { handleError(error: $0) }) else { return }
  
  guard let productImages = fetchProductImages(products).getValue(onErrorDo: { handleError(error: $0) }) else { return }
  
  guard let processedImages = processProductImages(productImages).getValue(onErrorDo: { handleError(error: $0) }) else { return }
}
extension Result {
  func getValue(onErrorDo: (Failure) -> Void) -> Success? {
    switch self {
    case .success(let success):
      return success
    case .failure(let error):
      onErrorDo(error)
      return nil
    }
  }
}

I've written synchronous variant for simplicity, async variant can also be done. May be it will help

1 Like

This, adapted from my earlier example, seems simpler:

1 Like

I totally agree that there's something missing here and I suggested a solution to this problem here:

With my suggested change, you would be able to write:

guard let products = fechProducts() catch {
    handleFailure(error)
    return
}

Feel free to provide your usage example in the thread, too.

4 Likes