I’m hesitating over the word “returning.” There are a few different things you could mean by it.
By definition, an asynchronous call is expected to return before the desired action is completed. The calling code could be long-gone before an asynchronous error even occurred; a throw from the async task would have nothing to catch it. Errors resulting from the action would be reported to the completion closure, or to a possible error closure, or not at all. Which you design for depends on what you expect will satisfy the needs of the applications that use your API.
An async call might fail immediately, before it returns — you’re trying to write to a closed descriptor, for instance — so the call can return an error value. The options are
- Traditional: Return a value that signals failure (in Swift, usually a nil) and sets an optional NSErrorPointer to an NSError. Objective-C callers understand this.
- Enum: Return an enum with an error case and maybe associated values characterizing the error. I like this for Swift 1.x because I hate by-reference return values and I wasn’t interested in offering a bridge to Objective-C.
- Swift 2+: Throw. The caller gets a clearer flow of control, at the expense of bridges to earlier code.
Choose what you need; there are always tradeoffs.
Whether the asynchronous call also calls a completion/error closure in the event of an immediate error is an independent question. By instinct, I’d do so: The closure would likely include things that have to happen no matter how promptly the task terminated. Or, the calling code might be in a better position to clean up. Either, or both, depending on the needs of the caller. If your async function returns/throws error in-line as well as allowing a completion/failure closure to handle the error, the caller has choices.