Write a Networking Layer in Swift 4 using Alamofire 5 and Codable Part 3: Using Futures/Promises

In the first part of this series we have created the API Router of our networking layer and then in the second part we have added the logic of performing and parsing the network requests. In this part we will try to improve the way we perform the requests, we will see how we can use Futures/Promises to work better with asynchronous code.

Asynchronous code

Asynchronous code is more difficult to reason about compared to classical blocking code. Here is a common mistake done by developers especially when they just start working with asynchronous code:

The problem with the snippet above is that the return result will be always nil because we will return the value before getting the result from the server. I have added comments with numbers to indicate the order of execution of each instruction. Note that we return from the function before waiting for the server response.

We can understand why would someone write a code similar to the above snippet because in the synchronous world we know that our program is executed line by line, one line at a time. Each time a function is called, program execution waits until that function returns before continuing to the next line of code. But this is not valid when dealing with asynchronous code because when we execute something asynchronously, our program can move on to another task before it finishes. This is useful for example when dealing with network requests. So how to fix the problem above ?

Callback mechanism

You are probably using the callback mechanism to deal with asynchronous code in order to avoid the issue mentioned in the code snippet above. Here is how we usually work with the network requests functions:

Note here that we don’t return anything because we will get the result using a callback in this case it’s named completion which is just a closure (anonymous function). What we did above is passing an anonymous function that takes the expected result as parameter in our case a Result of type User, and then later when we get the result from the server we execute the callback completion and passing the retrieved data as parameter.

Callback mechanism solves the issue but is it the best way to work with asynchronous code ?

Let’s see this example:

We can see that if we have nested callbacks it starts to get messy, the code is not friendly in term of maintainability, readability and control, and this leads to the Pyramid of doom, Callback hell and error handling issues.

Pyramid of doom

So how to avoid these issues when working with asynchronous code especially when it involves nested calls ?

Future/Promise

One of the options is using Future/Promise, but what’s is that exactly ?

The terms future, promise, delay, and deferred are often used interchangeably. From now on I will just use the term future (check here for more details about the difference between Futures and Promises)

A Future is a way to represent a value that will exist (or will fail with an error) at some point in the future. It’s a placeholder for values that are currently unknown due to waiting for the network, long and complex computations, or anything else that does not immediately resolve.

A Future represents a placeholder for a value not necessarily known when the future is created. This lets asynchronous methods return values like synchronous methods, instead of the final value, the asynchronous method returns a promise of having a value at some point in the future.

Do you remember the first snippet of this article where we mentioned that a developer may wrongly try to return immediately the value of the asynchronous method ? Actually Futures let us do just exactly that, but we will return a immediately future instead, which represents the eventual result of an asynchronous function. Here is how it looks like:

static func login(email: String, password: String) -> Future<User>

When the function returns, the returned future is not yet completed — but there executes a background task which computes the value and eventually completes the future.

Using a special type to represent values that will exist in the future means that those values can be combined, transformed, and built in systematic ways. 
With Futures, composing those asynchronous operations becomes much easier, so we can perform a chain of dependent asynchronous operations sequentially to avoid the Pyramid of doom.

There is plenty of third party libraries that let us work with Futures but for the seek of simplicity I will be using my own library PromisedFuture to demonstrate the usage of Futures and to improve the networking layer that we have created in the previous part of this series.

PromisedFuture

First, note that you can use any library that you want to work with Futures and Promises, I have opted for PromisedFuture library because it’s very lightweight library (around 50 lines of code) and it’s a very simple implementation of Futures. I may write a separate article about the implementation of PromisedFuture but now we will just focus on the usage of Futures in our networking layer.

Let’s explain the usage of PromisedFuture before we use it in our networking layer.

How to create a future ?

We can create a Future to represent an asynchronous task by using the initializer init(operation: _)
example:

It’s simple to create a Future, we just init the Future with the operation and resolve the Future with calling the completion with a success and passing the value or reject the Future by calling completion with a failure and pass an error.

What we have done is basically save the way of retrieving the data into the variable usersFuture

How to retrieve data with a future ?

In our previous example we have created a Future usersFuture but now how we can get the data ?

Simply we can use the execute function of the Future

For more information about the usage of PromisedFuture please check the GitHub page of the library.

You may wonder that this looks like using the callback mechanism and you can’t see the benefits of using the Future. Actually it will get interesting once we start chaining and transforming the Futures.

First let’s convert our APIClient to work with Futures:

Basically what we have done is to remove all the completion blocks and return a Future instead. Note that instead of computing the return variable right away and call the completion block, now we save the way of how we compute the variable without firing the network call, the benefits of doing that is that it let us chain and transform the asynchronous tasks before executing.

Let’s see first how we use our networking layer with future.

We have a clean interface to execute our asynchronous tasks. But let’s see how using Futures can help us to better deal with chained asynchronous tasks to avoid the Pyramid of doom and Callback hell issues.

Let’s say that we want to:

  1. Login
  2. Get all articles of the logged in user (overview of articles).
  3. Get full details of the last article of the logged in user.

Note here that we have a depending asynchronous tasks because we need to login and after finishing then use the user information to make a new request to fetch the articles by the id of the user and finally when we get all articles we use the id of the last article to make a new request to get the full information of the given article. Using callbacks we will have something like that :

Let see now how it looks when using Futures:

You can see that our code is much better in term of readability, with just few lines of code in a declarative way. Note that we have first chained and transformed our tasks and then only execute the task, so we could perform a chain of dependent asynchronous operations with one completion block at the end.

You can find the full example project using Futures in the branch named “futures”:

Summary

We have seen that working with asynchronous code can get messy when using nested callbacks, and we have discussed how using Futures can help us to work better with asynchronous code.

Futures are suited for any asynchronous action that can succeed or fail exactly once, such as HTTP requests. If there is an asynchronous action that can “succeed” more than once, or delivers a series of values over time instead of just one, then using Signals or Observables is more relevant, and that’s what we will discuss in our next article, we will use reactive programming (ReactiveCocoa and RxSwift) for our networking layer.

Please feel free to comment and suggest any possible improvement, and please note that I’m not able to reply to any questions/comments here on Medium. Instead, please open a new issue in the example project GitHub repository:

Stay tuned and don’t forget to follow to be notified once the next part is published.

Thank you for reading.


You can find me on Linkedin and Twitter