Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Asynchronous Error Handling in Swift 2.0

Asynchronous Error Handling in Swift 2.0

As Swift 1.0 did not include support for exceptions, a common error handling pattern was to use a Result enum that either took on a value on success, or an error on failure. In a way, this was superior to using exceptions, since being a instantiable type, Results could be passed around freely, enabling asynchronous code to use the same error handling mechanisim as synchronous code. Using Swift's robust support for custom operators, this also enabled Railway Oriented Programming for more functional oriented programers.

Swift 2.0 introduces throwable errors, which are similar to exceptions. However, like exceptions, thrown errors propogate only down the call stack and can only be caught in the same thread that threw them. In order to propogate the errors upwards (such as to callback routines) and across thread boundaries it is necessary to catch the errors and pass them along in some sort of instantiable type, and however this is done, it is preferable to be able to later handle those errors the same way as all other thrown errors.

One way to do this is through a thowing closure that either returns a value or thows an error, as detailed in this blog post. However, this strikes me as odd as a closure can do anything and the desired behavior is very well defined.

Through a simple extension to the familiar Result enum can serve this purpose.

First here is the definition of a typical Result enum:

enum Result<T: Any> {
    case Success(T)
    case Failure(ErrorType)
}

Many versions of the Result enum use NSError for the failure case, or accept a second type parameter for the failure case. ErrorType is a new protocol introduced in Swift 2.0 that all throwable errors, including NSError conform to.

Here is the extension to Result:

extension Result {

    init(_ f: () throws -> T) {
        do {
            self = .Success(try f())
        } catch let e {
            self = .Failure(e)
        }
    }

    func unwrap() throws -> T {
        switch self {
        case let .Success(x):
            return x
        case let .Failure(e):
            throw e
        }
    }
}

The unwrap method returns the value for swift .Success and throws the error for swift .Failure. The initializer provides a convenient way to create a Result object for an arbitary block of code, simplifying the implementation of asynchronous functions.

Here's an example of how an asynchronous library function might be implemented:

func findAnswerToTheUltimateQuestion(completion: (Result<Int>) -> ()) {
    //Computers are much faster today than they were in 1978, so this only takes a week
    let latency: Int64 = 1000*1000*1000*60*60*24*7;
    let time  = dispatch_time(DISPATCH_TIME_NOW, latency);

    dispatch_after(time, dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
        let result = Result {
            //any number of statements that may potentially throw
            return 42;
        }

        completion(result);
    }
}

Thanks to type inference, there is no need to specify the type for the success case. If it weren't for syntax coloring and the capitalization of the R, it may not be apparent at a glance that Result isn't simply a language level construct.

Here's how this function might be used:

findAnswerToTheUltimateQuestion { result in
    do {
        let answer = try result.unwrap()
        dispatch_async(dispatch_get_main_queue()) {
            print(answer);
        }
    } catch let error {
        dispatch_async(dispatch_get_main_queue()) {
            print(error);
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.