Parallel programming with Swift: Promises
Concurrency is getting more and more relevant in our daily work. In my last articles (Parallel programming with Swift: Basics, Operations), we have looked into tools Apple provides. This time, let’s look into something not officially supported.
Just to recap:
Concurrency is about the ability to do work at the same time.
Imagine having a long lasting task. It takes a while, and in the end, we get a result. This could be for example downloading a file. The result would be an image. At the same time, we know we will set this image to our UIImageView. So how could we implement this?
The most trivial version would be to download the file via NSURLSession and give it a closure.
We all know, how to do this. So no problem here. But let’s make it a little bit more complicated. We don’t have the link to the file, instead, we have to request it, from our backend. Doing this would contain requesting the link, parsing the response to our expected format, requesting the image and then setting it.
This could get more and more complicated for simple use cases like this one. In the end, we are in Callback-Hell.
Instead, we could create a delegate and react to this. It will get hard to maintain as soon as you have more than 2 requests.
Looking into Operations, we would split these tasks and write them this way.
But as you can see, it’s quite hard to follow the use-case’s logic. So which other options exist?
There is an idea, which considers creating variables containing values in the future. It doesn’t need to contain its value right at this moment, but at some point, it will have the value and then we can execute code on it.
This is nothing else than a Promise — an assurance to deliver a value at some point in time. For a Promise, you write code on the idea, that this variable will either contain a value or an error at some point. And it will only be executed, as soon as this promise is held.
Composition and Creation
A Promise consists of a closure with 2 callbacks. You can fulfill the Promise, by calling fulfill() with the corresponding value, or you can reject it with an error.
To create a Promise you just have to wrap the code you want to create the value.
Promises are separated into multiple parts: The promise itself, the code for the success (if it contains a value) and code in case of an error.
A promise won’t execute any code, as long as there is no code, which will respond to its value. To add this code, we use `.then()`. This still doesn’t decide on the time it’s executing. It just states that the promise may start if it is inclined to.
Since often we have to create chains of code, we can not only return values, but we can also return promises.
In case of any error within the responder chain, the execution flow will jump to the catch clause and not continue executing the other then() closures.
There are multiple libraries for promises. Just recently Google released their own version. I will use PromiseKit as it is quite mature and has been worked on for years. From my experience, their issues response time was very fast, just within several hours. Furthermore, PromiseKit provides a lot of extensions you can use within iOS to ease the process of using it. It does contain some special cases due to Swift and their own interpretation of Promises but anyway let’s dive in and see how we can use it.
PromiseKit supports nearly all ways to install it. You can do it via Cocoapods:
or do it manually. The choice is yours! Having done so, we can get started.
As described, a promise consists of a closure with two different callbacks. One callback is to fulfill and one is to reject the promise. In PromisKit this is a little bit different. We have a seal object, which has multiple methods including fulfill(result) and reject(error). It does also contain resolve(result, object) which will automatically determine, what the status of the promise is.
Requesting an image from a backend would look like this:
As you can see, promises always have the option to fail. But we do have cases which won’t have an error (e.g. returning a static text). For this PromiseKit offers a special kind of Promise. It’s called guarantee:
Having created a Promise we can now activate it. This is done by using then().
then() always has a return value which is different to the promise received. If its body is just one line, Swift tries to do type inference. Sadly, this doesn’t work most of the time and we get a rather unspecific error message:
Cannot invoke ‘then’ with an argument list of type ‘(() -> _)
This can be fixed by specifying the closure parameters and the return type.
You can finish your chain by using done(). It is the typical end of the success part of your chain and you can’t return a promise.
The last part of your usual promise chain (except you use guarantee) is catch(). This is used to react to any kind of error during your execution. If any promise within your chain will be rejected, the chain will be discontinued and the execution path will jump to the catch closure. In this closure, you can add your error handling, such as showing a message to the user.
As always, there are exceptions to cases such as these. Sometimes you don’t want an error to cascade and have e.g. a default value. This can be done with recover().
Our prior example of fetching an
imageurl from the backend and then the image itself would look like this:
Further Chain Elements
Now that we’ve looked at the basic elements of our promise chain, let’s elevate them to the next level. We can use some syntactic sugar to start the chain by using firstly().
If we want some code to execute always at the end of the promise, we will use ensure:
Looking back at our example, we can add the network indicator:
Sometimes we want to return the same promise we’ve received. To do so, we can use get().
This is especially useful if we need to execute multiple commands on the same result.
Waiting for multiple asynchronous commands to execute is either slow or hard. It’s slow in the case we execute them synchronously, or hard if we try to handle all the different options for callbacks. In PromiseKit there is when(). In when() you add all promises you want to execute at the same time and it will wait for all of them to resolve before it continues.
We've been using done() instead of then in the last few promises. It basically tells the promise that the chains ends here and no return value will exists, while then() always has a return value.
There are more elements for the promise chain such as:
- map() which requires you to return an object or value type
- compactMap which requires you to return an optional. Nil is an error
As always, when working asynchronously we have to consider threading. Here is how it works with promises: All promises are executed on a background thread, but the chain itself (then(), catch(), map(), etc…) is executed on the Main thread. This is important to know, as it can result in some kind of unexpected behavior. Imagine parsing the response of a promise into your persistence layer. If the response is rather small, there is no problem doing it on the Main thread, but it’s not guaranteed to be like this. A larger response can result in frame drops or even your screen freezing. To solve this, you can either write your parsing as a promise, or you can manually change the thread of then() closure.
It is advised though, to just write promises and handle threading within them, as it’s less error-prone.
To ease your conversion to promises a little bit, let’s look at some patterns developers are used to:
Sometimes we have the case, when we want to delay an execution for a specific amount of time. This can be achieved by using after().
Since we know after(), we can also create a timeout. Of course, there are options to set a timeout for different use cases (e.g. network requests in NSURLSession) but sometimes we want to change it to a smaller value or add a timeout to a promise, which doesn’t time out by default. This can be done by using race()
Be aware, that both promises in race need to return the same value. An easy way to achieve this is by using .asVoid().
As always with network requests, it can happen that the connection is down and we want to retry the request. This can be done by using recover().
One of the major UIKit patterns is the delegate pattern. We can wrap it in promises, but be aware, if you do this it might not fit your needs. Promises only resolve once, so trying to wrap a UIButton will lead to problems. In cases you just require one response, you can do so by storing the seal object and resolving it in your delegate.
One thing to note, there can only be one request like this at any time, as you store the seal object.
Saving previous results
Sometimes you might want the result of a prior promise in your next promise. This can be achieved with Swift tuples. Imagine a login sequence in which you first log in and then want to store the username with the access tokens. You could do it this way:
An interesting part of promises is that they have a single execution. If you add two different chains to a promise, it will execute only once, but you can have two different flows for this one execution (this includes also catch()):
One difference to Operations (as introduced in my last post) is, there is no cancellation mechanism build into promises. To have this option, we have to implement it:
Displaying ViewController is a little bit similar to wrapping delegates. The difference is you store the promise itself. Furthermore, the presentee has to know how to present itself and dismiss itself, which is kind of unfortunate. Still, if you want to wrap everything into promises, this is an option to do so:
This time we’ve looked into promises as a way to master concurrency. It has its advantages over operations, but also disadvantages. Some would now start arguing about state, which is supposedly better when using promises, but in my opinion, this is not a reason to use them. They improve readability and make it easier to control concurrency. I think they hide a little bit too much, as programmers need to be aware of what they are doing and not hope a framework takes care of all their mistakes.
Having worked with promises for quite some time, I can say it makes reasoning about your code easier as long as you know, what Promises are. If you don’t, Promises are still easier to reason about than operations.
One last thing, since this is not a language feature, it is different from other implementation of promises such as the ECMA-Script version.