PromiseKit is a library that allows us to add Promises for swift.

What are promises?

A Promise is an object that represents the result of an asynchronous operation.

Asynchronous request to not block the clientThe more common example would be that of a REST request. As we all know, petitions to a remote server can take their sweet time, and we do not want to have the client , be it a mobile application, or a plain browser, locked up till the server responds.

So we make asynchronous petitions : the application does the petition, and then handles the control back to the client. At some point in the future the server will answer, and the client can process the data returned by the server.

The callback approach

You can use callback functions to process the data returned by the server: this code, that I used to demonstrate how to make Rest request with Alamofire, and ObjectMapper, is and example of an asynchronous function that passes callback functions as parameters:

func getCostumers(completion:@escaping (Array<Customer>) -> Void, failure:@escaping (Int, String) -> Void) -> Void{

        let url: String = "https://jsonplaceholder.typicode.com/users"

        self.manager.request(url).validate(statusCode: 200..<300).responseJSON { response in
            switch response.result {
            case .success:
                // let customerArray = ...
                // completion(customerArray)

            case .failure(let error):
                failure(0,"Error \(error)")
            }
        }
    }

The execution flow in this example is like this:

  • An asynchronous call is done to the server
  • The client does not wait for the server response: getCostumers returns immediately, and the execution of the program continues.
  • At some point the server returns the list of Customers, and the function completion is executed.

Using promises

Asynchronous programming is not easy. Although the example above works fine, things can get a bit hairy when you have to deal with multiple asynchronous request.

Consider the following examples:

  • You got to do multiple concurrent petitions to N servers in a view. You display the typical loading symbol at the beginning of the process, and you need to hide it once all the request have finished. Using callbacks you will have to implement some sort of queue to keep track of the status of the different requests.
  • You need to do multiple, non-concurrent, petitions, one after another. You will need to nest your calls, resulting in a code more difficult to read, and to maintain.

When you use promises, instead of using callbacks you need to reprogram your request to return a promise object. Once the promise is resolved it will be either fulfilled (success), or rejected (error).
You can react to the promise using different methods:

  • then: used when the promise is fulfilled
  • always: this will execute always regardless of whether the promise is fulfilled, or rejected
  • catch: used when the promise is rejected

You can also chain the different calls, which results in a more readable code.

Rewrite an Alamofire request using PromiseKit

The easiest way to understand how promises work, and what benefits they provide it is to take the example I used at the beginning of this post, and modify it so that it uses promises.

Luckily for us Swift support for promises is available thanks to the PromiseKit library.

Install PromiseKit

Add the following line to your Pod file, and run pod install:

pod “PromiseKit”, “~> 4.0”

Import the library:

import PromiseKit

Rewriting the getCustomers function

For starters our function needs to return a Promise object, that acts as a wrapper of the result of the asynchronous server request. Given that our request must return an array of objects Customer, the signature of our function must be modified accordingly:

 func getCostumers() -> Promise<[Customer]>
    {
       //...
    }

Notice that the callback function parameters are gone.

Now we must modify the body of the function so that it fulfills, or rejects the promise depending on if the request succeed, or failed:

   func getCostumers() -> Promise<[Customer]>
    {
        let url: String = "https://jsonplaceholder.typicode.com/users"
        return Promise { fulfill, reject in
            self.manager.request(url).validate(statusCode: 200..<300).responseJSON { response in
                switch response.result {
                case .success:
                    //to get JSON return value
                     guard let responseJSON = response.result.value as? Array<[String: AnyObject]> else {
                            reject(NSError(domain: "domainN", code: 0, userInfo: [NSLocalizedDescriptionKey: "Some error readinf response"]))
                            return
                         }
                     guard let customers = Mapper<Customer>().mapArray(JSONArray: responseJSON) else {
                         reject(NSError(domain: "domainN", code: 1, userInfo: [NSLocalizedDescriptionKey: "Some error mapping the object"]))
                         return
                     }
                    fulfill(customers)
                case .failure(let error):
                    reject(error)
                }
            }
        }

    }

And the function will now be used like this:

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    var datasourceItems:[Customer] = []

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        CustomerService.sharedInstance.getCostumers().then { customers -> Void in
            self.loadCustomers(items:customers)
            }.catch { error in
                print(error)
        }

    }

    func loadCustomers(items:[Customer])
    {
        for item in items{
            // do stuff
        }
    }
}

Chaining request

As I said at the beginning of this post, when promises really shines is when you have to deal with multiple request.

Imagine the following scenario: I need to get the data of a customer, find out for which company he works, and retrieve the data of the company. Using promises the above example would look like this:

        showSpinner()
        CustomerService.sharedInstance.getCostumer(id:2).then { customer -> Promise<Company> in

			return CustomerService.sharedInstance.getCompany(id:customer.company.id)
            }.then { company -> Void in
            	self.loadCompanyData(company:company)
            }.always {
				hideSpinner()
			}.catch { error in
                print(error)
           }

Much better than the non-promise equivalent, right?

self.manager.request(urlCustomer).validate(statusCode: 200..<300).responseJSON { response in
            switch response.result {
            case .success:
                self.manager.request(urlCompany).validate(statusCode: 200..<300).responseJSON { response in
					switch response.result {
            		case .success:
						hideSpinner()
						//finally do stuff with the customer company
					 case .failure(let error):
						hideSpinner()
                		failure(0,"Error \(error)")
				}
            case .failure(let error):
				hideSpinner()
                failure(0,"Error \(error)")
            }
        }

Look, for example, how in this version we have to remember to hide the spinner in 3 different places: when the last request succeeds, or if any of the request fails.

Hope it helps! If you found this post interesting, you can subscribe to my blog (click at the follow button at the bottom ) so you can be notified of new posts.

Advertisements