Better Promises in Swift

Tomas Hakel
INLOOPX
Published in
5 min readMay 25, 2018

A simple promise

Asynchronous code is quite common, but if we simply add more async calls in one place we’ll end up with massive methods and ten levels of indentation. In other words, we’ll successfully have implemented callback hell.

Surely we can do better than that! We could try splitting it into several functions… but that can lead to other problems…

A downside of this approach is that control flow is hard to track. From the methods signature, it’s not clear which methods are called. This is a good thing if the method call is a part of the implementation of our method’s responsibility, but if it calls the next item in our process pipeline, then it quickly becomes controll flow hell. This is why we have callbacks in the first place!

We want to have high level control flow defined in a single place, and put the implementation details somewhere else, properly encapsulating them. A possible solution is to use a promise.

So, what’s a promise? A promise (also referred to as a “future”) refers to constructs used for synchronizing program execution. They describe an object that acts as a proxy for a result that is initially unknown, usually because the computation of its value is yet incomplete [1].

Why should I use a promise? To keep code clean and well structured. It’s a simple option for avoiding things like callback hell and controll flow hell. We always want to keep code indentation as shallow as reasonable, and a promise is a good way of achieving that.

Promise hell?

Depending on how they’re used, promises can simplify code, but they can also make it more dificult to understand. Whilst doing a code review, I came across essentially this wonderfull thing:

Quite a piece of work, isn’t it? One can tell there’s a problem at first glance, because the code doesn’t look aesthetically pleasing (which is, in my humble opinion, a very good metric for judging code quality). Unfortunately it’s not too uncommon for code using promises to degenerate into something like this. So what’s the problem? It can be hard to tell what a .then block does and they can get quite massive. It also violates the single responsibility principle [2] since each block has a separate responsibility but they’re all contained in a single method.

Another problem we have is the lack of separation of abstraction layers. A method should only contain code that occupies a single abstraction layer, and in our case, we arguably have two: 1. the code that manages the flow of our process, and 2. implementation details for the steps making up the process.

A better promise

A simple solution would be to add comments at the beginning of each block describing what it does. A better solution would be to write self documenting code which, in this case means extracting the bocks into separate methods. Just be sure to name the methods properly. And you also need to name the methods properly (I’ve said it twice because it’s important, in case you were wondering). The original problem was that we couldn’t easily tell what was going on and replacing that with something vague won’t be helping much, right? Even if the contents of the block contain a lot of code it’s more manageable if it’s contained in a method and we also solve the abstraction layer problem.

Promise heaven?

We can take things a step further thanks to the fact that Swift functions are first class objects. This means we can pass them into other functions where a closure is expected. The promisseHell example would turn into something like this:

There are several promise libraries we can choose from. The most popular is probably PromiseKit [3]. Personally, I prefer the then framework [4] because its methods don’t require labels which makes it more concise (i.e. in then we would write .then(handler) while in PromiseKit it would be .then(execute: handler)). Either one will do, but you will need to write slightly different code depending on what the chosen framework expects.

Promise overkill?

Should we make a new function for each then block even if they contain just one or two lines of code? The answer to that is to be pragmatic. Sometimes going too far with good intentions can certainly be overkill. Consider the context, compare the alternative versions and use the one that makes the most sense. Remember, the metric we’re maximising is code readability. If our logic is very simple (e.g. we just call a callback when we get a result) it might not make sense to overengeneer things. But let’s not forget to be consistent with our decisions.

Promise nirvana

When passing in a method instead of a callback, the promise library expects a very specific header but what if we want to use more parameters? With closures we would simply capture the values in the outher scope. We could always just do .then { workWork($0, parameter) } but that would be a little inconsistent. So, do I write an extension? Send in a tuple? Or hack it in some other horrible way? No, of course not. Why reinvent the wheel when we have curry! /* TODO: insert a joke for fans of indian cuisine */

Currying allows us to turn a function with several parameters into several functions that return functions with a single parameter each. This means we can pass in a method with the expected signature, while retaining the approach from the previous section. If one hasn’t used it before, currying may sound like something difficult to get one’s head around but it’s actually very simple. We just import the curry library [5] and .then { workWork($0, parameter) } becomes .then(curry(workWork)(parameter)). Our final code can look something like this:

Fun fact: currying used to be a part of the Swift language but was, unfortunately removed. [6]

In conclusion: using promises can help us improve the clarity of our code but we need to use it properly or we’ll just make things even worse for ourselves. We can swap closures for methods and use curry to further increase readability while keeping things concise and consistent. Doing so we can hopefully move a step closer to writing better software.

Sources

[1] https://en.wikipedia.org/wiki/Futures_and_promises

[2] https://medium.com/inloopx/cleaner-architecture-on-ios-ac4027b85d1f

[3] https://github.com/mxcl/PromiseKit

[4] https://github.com/freshOS/then

[5] https://github.com/thoughtbot/Curry

[6] https://github.com/apple/swift-evolution/blob/master/proposals/0002-remove-currying.md

--

--