Chaining Asynchronous Functions in Swift
In this article, we will see how we can use function composition in Swift to chain multiple asynchronous requests (without necessarily going for RxSwift).
Edit: Brandon Kase pointed out that this kind of composition is actually called a Kleisli composition and the correct operator to use was the fish / Kleisli operator:
>=>
(instead of|>
).
[You can download the companion Playground for this article.]
Say we have a payment validation application that has the following models:
Starting from a customer identifier, we want to know if the customer can make an online purchase. We create a customer with an identifier. The other properties will need to be fetched from the server individually. Therefore in order for a customer to be able to make a purchase, we need to fetch the name, billing address and the credit card information.
A generic request to a web service might look like the following:
We take some input, that we use to make the request. When the request successfully completes, it transforms the data into the expected ResponseType
, wraps it in into the now famous Result
type and calls our completion block with that result. If an error occurs, it is wrapped in a Result
and passed in the completion.
Here is the interface of our request API that is based on the above signature:
To make the validation that we talked about, this is the code that we might actually write somewhere in our app:
The above snippet clearly indicates that chaining requests in this manner will drive us mad one day. It is very difficult to tell what is exactly happening here, simply by glossing over the code. Let’s try to come up with something that resembles this diagram:
As you should have guessed, we will use function composition to achieve that.
Let’s us take our generic request function:
func fetch(_ input: InputType, completion:(Result<ResponseType>) -> Void)
and represent it with the following alias:
public typealias Request<T, U> = (T, @escaping RequestCompletion<U>) -> Void
In our case, U will map to Result<ResponseType>
.
Next, we will define an operator that will allow us to pipe our requests.
Breaking down the >=> operator
This operator is generic over 3 types: T
, U
and V
and takes 2 functions f
and g
as parameters where:
f
takes aT
and completes with aResult<U>
g
takes aU
and completes with aResult<V>
.
It returns a composed function with a signature: Request<T, Result<V>>
i.e a function which takes the input of f
and completes with a Result<V>
.
Note:
- input is of type
T
- combineCompletion is of type
RequestCompletion<Result<V>>
.
This returned function’s implementation starts by applying f
,then in f
’s completion block, it switches over a Result<U>
.
- If
f
completed with a.success
, it curries on with the success value and callsg
with it. Wheng
completes (with aResult<V>
) it executescombineCompletion
with thatresult
. - If
f
completed with a.failure
it does not move any further and callscombineCompletion
with the corresponding error.
With that in place, our complicated validation code above becomes:
We first compose a validation
function with our 3 requests. Then to perform the actual validation, we just pass in the customer identifier and in its completion block we switch on the result
to see if we managed to get all the required properties of the customer.
That’s all there is to it!
Conclusion
Leave yours in the comments section.