Designing a workflow with functions that are capable of failingAlexey SavchenkoBlockedUnblockFollowFollowingFeb 25There are many situations in a routine work where functions we use are capable of failing, e.
throw errors, return empty optionals etc.
When “empty optional” is a poor man’s way of error handling, throwing an error is something that is supported by Swift out of the box.
Throwing functions were go-to way of error handling, but there is a way to design more convenient syntax for fallible functions.
Upcoming release of Swift 5 will include Result<T> type that enables modeling outcome value of some function as successor failure.
General definition of Result<T> goes as following:As you can probably notice Result<T> is much like Optional<Wrapped> from Swift’s stdlib.
But instead of returning empty Optional (.
none case or nil), we may wrap Error value to failure case and handle it appropriately.
It’s also possible apply map and flatMap to a Result value to achieve chain-like interactions (like Optional chaining).
Example above may be cumbersome, but I hope it proves the point.
But what if it’s needed to try again in case of `failure`?There might be cases when it’s needed to “retry” a function if it fails.
Most trivial is the case of API call, for example an API call for sign-in of a user and situation when network call is interrupted or whatever.
In this case you might think of an if-else or switch dance and some local variable to keep track of number of retries.
This might do if this behavior is needed for some local case, but if it’s needed to scale such behavior to multiple places, things might get ugly.
In RxSwift world there is a retry operator that responds to an onError notification from the source Observable by not passing that call through to its observers, but instead by resubscribing to the source Observable and giving it another opportunity to complete its sequence without errorMy idea was to encapsulate mentioned behavior to a completely generic and modular way for sync and async functions.
Please see listing below for an implementation:Code above describes wrapper of a generic sync funcion that takes some Input value and returns Result<Output>.
For sake of simplicity example that demostates usage will rely on random value evaluation rather then some specific application, hope this will not affect an understanding of application possibilities of this approach.
Code above describes application of wrapper over sync function and it’s execution.
Wrapped function may be executed up to 3 times and completion closure will be invoked either with .
success or .
failure in the worst case.
Same approach can be implemented for async functions:Example of application follows bellow, it’s almost identical to a sync example but has type signature and behaves like async function:Make fallible functions a bit smarterAs I mentioned before one of many applications of “retry-able” behavior of a functions are API calls.
Provided implementation already accomplishes that task, but it lacks one useful behavior — delay between consecutive retries.
To add that functionality we need to provide target DispatchQueue and desired TimeInterval to our fallible operation wrappers.
Implementation of async version follows bellow:Particular application requires minor changes to adopt new behavior:As you can see, adding retry behavior to your existing functions requires almost no effort.
Source code is available on GitHub.
Complete implementation is located at FallibleOperation.
swiftPlease leave any comments!.All feedback is appreciated!As always , you can reach me though LinkedIn or Facebook.