Best Way to Handle Errors from Async Closures in Swift 2

Best way to handle errors from async closures in Swift 2?

there are many ways you can solve this, but i would recommend using a completion block which expects a Result Enum. this would probably be the most 'Swift' way.

the result enum has exactly two states, success and error, which a big advantage to the usual two optional return values (data and error) which lead to 4 possible states.

enum Result<T> {
case Success(T)
case Error(String, Int)
}

Using the result enum in a completion block finishes the puzzle.

let InvalidURLCode = 999
let NoDataCode = 998
func getFrom(urlString: String, completion:Result<NSData> -> Void) {
// make sure the URL is valid, if not return custom error
guard let url = NSURL(string: urlString) else { return completion(.Error("Invalid URL", InvalidURLCode)) }

let request = NSURLRequest(URL: url)
NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
// if error returned, extract message and code then pass as Result enum
guard error == nil else { return completion(.Error(error!.localizedDescription, error!.code)) }

// if no data is returned, return custom error
guard let data = data else { return completion(.Error("No data returned", NoDataCode)) }

// return success
completion(.Success(data))
}.resume()
}

because the return value is a enum, you should switch off of it.

getFrom("http://www.google.com") { result in
switch result {
case .Success(let data):
// handle successful data response here
let responseString = String(data:data, encoding: NSASCIIStringEncoding)
print("got data: \(responseString)");
case .Error(let msg, let code):
// handle error here
print("Error [\(code)]: \(msg)")
}
}

another solution would be to pass two completion blocks, one for success and one for error. something along the lines of:

func getFrom(urlString: String, successHandler:NSData -> Void, errorHandler:(String, Int) -> Void)

Is there a way to throw errors from asynchronous closures in Swift 3?

You cannot throw an error, but you can return an error:

First, you need to make your calling function asynchronous as well:

func execute(argument: someArg, completion: @escaping (Value?, Error?)->()) {
queue.async {
do {
// compute value here:
...
completion(value, nil)
} catch {
completion(nil, error)
}
}
}

The completion handler takes a parameter which we could say is a "Result" containing either the value or an error. Here, what we have is a tuple (Value?, Error?), where Value is the type which is calculated by the task. But instead, you could leverage a more handy Swift Enum for this, e.g. Result<T> or Try<T> (you might want to the search the web).

Then, you use it as follows:

execute(argument: "Some string") { value, error in
guard error == nil else {
// handle error case
}
guard let value = value else {
fatalError("value is nil") // should never happen!
}
// do something with the value
...
}

Some rules that might help:

  1. If a function calls an asynchronous function internally, it inevitable becomes an asynchronous function as well. *)

  2. An asynchronous function should have a completion handler (otherwise, it's some sort of "fire and forget").

  3. The completion handler must be called, no matter what.

  4. The completion handler must be called asynchronously (with respect the the caller)

  5. The completion handler should be called on a private execution context (aka dispatch queue) unless the function has a parameter specifying where to execute the completion handler. Never use the main thread or main dispatch queue - unless you explicitly state that fact in the docs or you intentionally want to risk dead-locks.

*) You can force it to make it synchronous using semaphores which block the calling thread. But this is inefficient and really rarely needed.

Well, you might conclude, that this looks somewhat cumbersome. Fortunately, there's help - you might look for Future or Promise which can nicely wrap this and make the code more concise and more comprehensible.

Note: In Unit Test, you would use expectations to handle asynchronous calls (see XCTest framework).

How to elegantly handle errors in methods using NSURLSessionTask with closures?

What suits best for this kind of problems, is the promises concept. The actions that you perform in your code can easily be considered as piped actions: get_server_respose | parse_json | execute_callback. In a synchronous approach this would be very simple to write and will provide clean/readable code. In an asynchronous world, things get complicated, and you can easily get lost in the callback hell.

With promises, you can chain async operations in a very similar way you would pipe the synchronous versions, and you can have only one failure point, at the end of the chain.

Here's how a promise-d version of your problem could look:

let task =  session.dataTaskWithRequest(request)
task.promisify.then({response, data in
guard let httpResponse = response as? NSHTTPURLResponse else {
return failedPromise(Error(...)}
}
guard response.statusCode == 201 else {
return failedPromise(Error(...)}
}
return jsonParsePromise(data);
}).then({ parsedDict in
print("Registration went successfully")
callback?()
}).then(nil, { error in
print("An error occured: \(error)")
})
task.resume()

Much cleaner, with tasks like json parsing in a separate (testable) unit, and with only one failure point.

P.S. You can go even further and promisify the registerNewUser function itself:

func registerNewUser(user user: String, password: String) -> Promise {
//... task setup
return task.promisify.then({response, data in
guard let httpResponse = response as? NSHTTPURLResponse else {
return failedPromise(Error(...)}
}
guard response.statusCode == 201 else {
return failedPromise(Error(...)}
}
return jsonParsePromise(data);
})
task.resume()
}

, which can be easily used by clients of the function:

registerNewUser(usernameTextfield.text, passwordTextfied.text).then({user in
print("Registration succeeded with user \(user)")
}, {error in
print("Registration failed with error \(error)")
})

Error Handling - Async Call

First of all, I don't think it's necessary to stack the layers the way you did, for example, by adding the validation functionality as a layer you are increasing coupling making that layer dependant of the layers below (parsing, networking, etc.), instead, why don't you separate validation to make it only dependant of the data?:

class ViewController: UIViewController {

var validator = InputValidator()

override func viewDidLoad() {
super.viewDidLoad()

do {
try validator.validateInput("INPUT")
try Fetcher.requestDataWithParams("INPUT")
}
catch {
handleError(error)
}
}
}

Now the validation functionality is not dependant of the other layers, so communication would flow like this:

View Controller <---> ParsingLayer <---> NetworkingLayer

I did rename the layers but they are not necessarily have to be like this, you can add or remove layers.

I think is going to be kind of complicated if I try to explain my approach, so I'm going to give an example using the previous layers, first the bottom layer:

class NetworkingLayer {
class func requestData(params: AnyObject, completion: (getResult: () throw -> AnyObject) -> Void) -> Void {
session.dataTaskWithURL(url) { (data, urlResponse, var error) in
if let error = error {
completion(getResult: { throw error })
} else {
completion(getResult: { return data })
}
}
}
}

I have omitted some sections of code, but the idea is to do any necessary step to make the layer work (create session, etc.) and to always communicate back through the completion closure; a layer on top would look like this:

class ParsingLayer {
class func requestObject(params: AnyObject, completion: (getObject: () throw -> CustomObject) -> Void) -> Void {
NetworkingLayer.requestData(params, completion: { (getResult) -> Void in
do {
let data = try getResult()
let object = try self.parseData(data)
completion(getObject: { return object })
}
catch {
completion(getObject: { throw error })
}
})
}
}

Notice that the completion closures are not the same, since every layer adds functionality, the returned object can change, also notice that the code inside the do statement can fail in two ways, first if the network call fails and then if the data from the networking layer cannot be parsed; again the communication to the layer on top is always done through the completion closure.

Finally the ViewController can call the next layer using the closure expected by the Parsing layer in this case, and is able to handle errors originated in any layer:

override func viewDidLoad() {
super.viewDidLoad()
do {
try validator.validateInput("INPUT")
try ParsingLayer.requestObject("INPUT", completion: { (getObject) in
do {
let object = try getObject()
try self.validator.validateOutput(object)
print(object)
}
catch {
self.handleError(error)
}
})
catch {
handleError(error)
}
}

Notice that there is a do catch inside the completion closure, this is necessary since the call is made asynchronously, now that the response has gone through all the layers and have actually change to be of a more specialised type you can even validate the result without having the necessity to make a layer for the validation functionality.

Hope it helps.

Swift Async Await dealing with fallback scenario

You should either return a non-optional array or an error is thrown.

The function can be reduced to

func fetch() async throws -> [Feed] 
{
do {
return try await remoteLoader.load()
} catch {
return try await localLoader.load()
}
}

If the remote data cannot be loaded the error is being caught and the code tries to load the local data. If this also fails the error is handed over to the caller.

If remoteLoader can throw multiple errors add a second catch scope for example if there is an unavailable error to fall back

func fetch() async throws -> [Feed] 
{
do {
return try await remoteLoader.load()
} catch Loader.unavailable {
return try await localLoader.load()
} catch {
throw error
}
}

You can even omit the second catch block if you catch a distinct error in the first one.

Swift throw from closure nested in a function

When you define closure that throws:

enum MyError: ErrorType {
case Failed
}

let closure = {
throw MyError.Failed
}

then type of this closure is () throws -> () and function that takes this closure as parameter must have the same parameter type:

func myFunction(completion: () throws -> ()) {
}

It this function you can call completion closure synchronous:

func myFunction(completion: () throws -> ()) throws {
completion()
}

and you have to add throws keyword to function signature or call completion with try!:

func myFunction(completion: () throws -> ()) {
try! completion()
}

or asynchronous:

func myFunction(completion: () throws -> ()) {
dispatch_async(dispatch_get_main_queue(), { try! completion() })
}

In last case you will not be able to catch error.

So if completion closure in eventStore.requestAccessToEntityType method and the method itself does not have throws in its signature or if completion is called asynchronously then you can not throw from this closure.

I suggest you the following implementation of your function that passes error to callback instead of throwing it:

func insertEventToDefaultCalendar(event: EKEvent, completion: CalendarEventError? -> ()) {
let eventStore = EKEventStore()
switch EKEventStore.authorizationStatusForEntityType(.Event) {
case .Authorized:
do {
try insertEvent(eventStore, event: event)
} catch {
completion(CalendarEventError.Failed)
}

case .Denied:
completion(CalendarEventError.AccessDenied)

case .NotDetermined:
eventStore.requestAccessToEntityType(EKEntityType.Event, completion: { (granted, error) -> Void in
if granted {
//insertEvent(eventStore)
} else {
completion(CalendarEventError.AccessDenied)
}
})
default:
}
}

Swift Error handling best practices

You must handle your exceptions by throwing errors and guard and checking if else everything.

This is an assumption that doesn't have to be true. If your code is structured correctly, you shouldn't have to check everything with if lets and throws.


I need general Swift error handling tips and best practices ...

Before you look at anything else, read the following pages in order. They should give you a good background on best practices.

  1. Error Protocol Apple Developer Documentation
  2. Error Handling - Apple's Swift Programming Language Guide
  3. Magical Error Handling in Swift - Ray Wenderlich

Im using Alamofire for calling services, which has closure, and calling it through another function with closure too. AFAIK I can’t throw error inside async code, so what is best practice for such case?

You can. A good practice is to pass a closure as a parameter to your service-calling function, then call the closure when the asynchronous operation is complete, like so:

functionThatCallsAService(completion: @escaping (Data?, NetworkingErrors?) -> ()) {
session.dataTask(with: request) { data, response, error in
guard error == nil else {
completion(nil, NetworkingErrors.returnedError(error!))
return
}
completion(data, nil)
}.resume()
}

enum NetworkingErrors: Error {
case errorParsingJSON
case noInternetConnection
case dataReturnedNil
case returnedError(Error)
case invalidStatusCode(Int)
case customError(String)
}

Is it favourable to have every function in the app throw errors? Just in case? Like checking every single value or result.

If you know for sure that a function or value won't be nil/cause a runtime error/throw an error, then don't check for it! But generally, according to the web pages above, you should check for nil and/or errors from calling a web service, interacting with the file system, creating a complex object, etc.


Can I have a Singleton Error handling module?

You technically can, but I don't see any reason to do so besides logging.

Closure with asynchronous method (Swift 2.0)

So a closure is a callback meaning it continue to run the code the rest of code after the call to that function is made, and then once it is ready it will run the callback function.

So if you want to do something after it has finished then you would put it in callback so where you put print("B")

If you can calculating something your code should not be linear because it will freeze the main thread and the screen would become unresponsive until the action has finished.

What are you trying to do after?

How can I handle a throwing closure with trailing closure syntax in Swift?

Per comments above, I can't throw directly. I ended up doing this:

try Alamofire.request(url, headers: headers)
.validate()
.response { response in
do {
let error = response.error;

if (error != nil) {
throw MyError(message: "No good")
}

// more logic

if (anotherCondition == true) {
throw AnotherError(message: "Error Parsing return data that I expected");
}

}
catch let error {
uiMessage = "";
if error is errorThatUserShouldSeeSomething {
uiMessage = error.localizedDescription;
}
ErrorHandler.handle(error: error, uiMessage: uiMessage);
}
}

I was honestly going to do the ErrorHandler.handle call, which is my custom handler anyway. It would have been nice to make it higher up in the stack though.



Related Topics



Leave a reply



Submit