google / promises

Promises is a modern framework that provides a synchronization construct for Swift and Objective-C.
Apache License 2.0
3.8k stars 294 forks source link

Extending promise validation to allow custom errors to be thrown #102

Closed elielamora closed 5 years ago

elielamora commented 5 years ago

It would be nice if validate allowed a custom error to be thrown if the predicate was false.

There is no way to discern what validation failure when there are multiple in a chain.

Promise<Int>(3)
  .validate{$0>5}
  .validate{$0%2 == 0}
  .catch{...}

This can arise when there are reusable promise subchains (each with its own prerequisites) which are arbitrarily arranged. When these are grouped there is typically one large catch-all at the end.

Something like the following would simplify the logic and make better use of the library.

enum NumberError: Error{
 case overFive
 case even
}
Promise<Int>(3)
  .validate(NumberError.overFive){$0>5}
  .validate(NumberError.even){$0%2 == 0}
  .catch{error in 
     switch error {
       case .overFive:
          ...
       case .even:
          ...
  }
}
ghost commented 5 years ago

Hi Eliel, thank you for raising the issue!

Are you looking to make validate's predicate 'throwable'? If so, do you mind adding a code snippet demonstrating what you had in mind?

Which cases would the predicate return 'false' vs. throw an error?

ghost commented 5 years ago

Hi Eliel, please feel free to open if this is still an issue. Thank you!

elielamora commented 5 years ago

.validate returns identity if the predicate is true. If the predicate is false it throws a fixed exception FBLPromiseErrorCodeValidationFailure which I initially wanted to be overridable by a user to allow a custom subclass of NSError to be thrown. This would be in hopes of separating the promise chain between the action to perform and the error handling logic.

I was able to accomplish this with a helper function leveraging .recover to catch that exception and rethrow the custom exception. Similar to what is shown below.

func validate(
  on queue: DispatchQueue,
  _ error: Error,
  _ predicate: Predicate<T>
) throws -> Promise<T> {
  return self
    .validate(on: queue, predicate)
    .rethrow(on: queue, error)
}

I then realized that it is more idiomatic to use the library in the following way:

Promise<Int>(3)
  .validate{$0>5}
  .catch{/* handle this error */}
  .validate{$0%2 == 0}
  .catch{/* handle this other error */}

Where the catch following each validate knows the 'type' of error needed to handle.

I will be closing the issue as the proposed changed doesn't need to be a part of the core library and there are other ways of accomplishing the same thing.