ErrorType

Hello!


I'm curious about the best ways to handle errors that occur in asynchronous callbacks in Swift 2. It seems that returning an NSError (or ErrorType) via a callback argument is still a workable option, but this does not jive well with the new try/throw/catch/ErrorType syntax.


What is the correct way to throw an asyncronous error with the new syntax?


Here's a complete example to demonstrate:


MyPlayground.playground

import Foundation
import XCPlayground
enum Either<L,R> { case Left(L); case Right(R) }
enum MyError : ErrorType { case Failure }
let succeed = arc4random_uniform(2) > 0

func performWorkSync_happy() throws -> String {
    // This is great!
    print("Sync start")
    if !succeed {
        throw MyError.Failure
    }
    return "Hello, World!"
}
func performWorkAsync_sad(callback : Either<String, MyError> / throws */ -> Void) -> Void {
    print("Async start")
    dispatch_async(dispatch_get_main_queue()) { () -> Void in
        // TODO: This isn't quite right....
        /*
        if !succeed {
          throw Example.Failed
        } else {
          return "Hello, World!"
        }
        */

        // TODO: This is not ideal, but it works
        do {
            try ({ if !succeed { throw MyError.Failure } } as () throws -> Void)()
            callback(.Left("Hello, World!"))
        } catch {
            callback(.Right(error as! MyError))
        }
    }
}

// MARK: Usage

do {
    let result = try performWorkSync_happy()
    print("Sync result: \(result)")
} catch {
    print("Sync error: \(error)")
}

performWorkAsync_sad { result in
    switch result {
    case let .Left(result):
        print("Async result: \(result)")
    case let .Right(error):
        print("Async error: \(error)")
    }
    XCPSetExecutionShouldContinueIndefinitely(false)
}
XCPSetExecutionShouldContinueIndefinitely(true)


Thanks!

Replies

Passing an ErrorType (or NSError) still seems to be the way to go in Swift 2; I believe the Swift/Objective-C interop session at WWDC confirmed this. I suspect that you'll be able to switch on the error and use ErrorType enums as cases, so you can still achieve a structure similar to using catch statements.

Thanks. This does seem like a valid workaround.


I'm curious if anyone else has a more optimal workaround.


If one does not exist, I hope the swift team will take notice of my feedback. I'd love to be able to write something along these lines:


func doAsync(callback : String throws -> Void) {
     if (success) {
          callback("hooray!")
     } else {
          throw MyError.Failed -> callback

     }

}

// MARK: Callsite

doAsync { result in
     // handle result
} catch {
     // Catch async thrown error
}


Thanks!

Honestly, it wouldn't be "throws" so much as "catches":

func doAsync(callback : String catches -> Void) {
    if (success) {
          callback("hooray!")
    } else {
          callback(throw MyError.Failed)
    }

}


But I suspect that there are enough weird cases in the frameworks (for instance, CloudKit's modifyRecordsCompletionBlock can take both a bunch of successfully-processed objects and an error indicating objects that failed) that it'd be difficult to develop a pattern like this.