03 January 2016

There’s a certain tedium in implementing asynchronous operations using the typical patterns common in apps. Typically every call requires a completion handler that either handles the result or handles an error.

I’ve taken a look at PromiseKit 3.0.0 as a potential means to reduce some of that effort due to the following advantages

  • escape from callback hells
  • treat async values almost like synchronous values
  • reduce cognitive overhead in implementing async operations

The essence of promises is that they turn asynchronous operations into function-like entities. The return value is obtained by thening a promise. The following code shows the definition of a promise based on a typical async operation and how the result from that async operation is obtained from the promise.

// Sample Promise function form that wraps an existing async op.
func somePromise() -> Promise<NSData> {
    return Promise { fulfill, reject in
        asyncOperationWithCompletion { data, error in			
            if error != nil {
                reject(error)
            }	
                fulfill(data)
            }
        }
    }
}

somePromise().then { data in
    // Do something with the data retrieved from the Promise.
}

As an important note, the async code is executed at the time the Promise function is called. Therefore, the result could be retrieved by thening the Promise when it is needed. In PromiseKit, there is even a when to allow actions to be taken as soon as the result is ready.

Chaining Promises

Instead of descending into a callback chain to perform multiple dependent operations, a Promise chain can be created in a function compositional way. The result of each Promise in this example is passed on to be ingested by the next Promise.

firstly { () -> Promise<T> in
    return somePromise() 
}.then { someResult in
    return anotherPromise(someResult) 
}.then { anotherResult in
    return anotherPromiseAfterThat(anotherResult)
}.then { anotherResultAfterThat in
    // Do something with the final result of the Promise chain.
}.error {
    // Handle an error somewhere in the chain.
}

WARNING: The documentation at promisekit.org may refer to .catch. This has been replaced by .error in Swift. Every place that you see .catch, .error should be used instead.

The Swift generic definition of a Promise as Promise<T> provides for a clear way of documenting return types such as Promise<String> directly within code.

Chaining async calls with completion handlers

A corresponding callback hell, I mean chain, would look something like

someFunction(completion: { someResult, error in
    if error == nil {
        anotherFunction(someResult, completion: { anotherResult, error in 
            if error == nil {
                anotherFunctionAfterThat(anotherResult, completion: { anotherResultAfterThat, error in
                    if error == nil {
                        // Do something with the final result of the completion chain.
                    } else {
                        // Handle the error from anotherFunctionAfterThat.
                    }
                }
            } else {
                // Handle the error from anotherFunction.
            }
        }
    } else {
        // Handle the error from someFunction.
    }
})

Really, it’s the error handling that explodes the callback chain. PromiseKit abstracts that error handling mess away into a single .error call.

Summary

PromiseKit may seem like a solution without a problem since you can get to the same end result without it. It could be a solution if you consider meaningful, readable code to be a problem worth solving.

The abstraction benefits that using the Promise type provides makes sense to me. It allows for keeping the logically related components of asynchronous operations together instead of split apart as with the callback form. A flat then chain is immediately preferable to a nested callback hell.

In Swift, the implementation of promises by PromiseKit is elegant in how it integrates with the language. The benefits are visibly apparent within my simple examples.

Aesthetics are not the only benefit. I also look forward to the effort that can be saved from not having to trace through compound callback mires during development and maintenance of my code.

My conclusion is that promises can bring clarity to the handling of asynchronous operations in Swift in a way that improves our ability to create great user experiences in apps.



blog comments powered by Disqus