Is there a way to throw errors out of async closures in Swift 3?

I am performing some functions in a test asynchronously using a send queue like this:

let queue: DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated)
let group: DispatchGroup = DispatchGroup()

func execute(argument: someArg) throws {
  group.enter()
  queue.async {
    do {
      // Do stuff here 
      group.leave()
    } catch {
       Log.info("Something went wrong")
    }
  }
  group.wait()
}

      

Sometimes the code inside the "do" block can cause errors that I have to catch later. Since I am developing a test, I want it to fail if the code inside the "make" block raises an error. Is there a way to throw the error without catching it inside the call to "queue.async"?

+3


source to share


2 answers


Rebuild your code to use queue.sync

and then discard your error. (Since your function execute

is actually synchronous, it doesn't matter.)

For example use this method from DispatchQueue

:

func sync<T>(execute work: () throws -> T) rethrows -> T

      



By the way, a good idiom when exiting DispatchGroup

is this:

defer { group.leave() }

      

as the first line of your sync / async block, which ensures that you don't accidentally get stuck when an error occurs.

0


source


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

First you need to make an asynchronous call function:

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 can say is a "result" containing either a value or an error. Here we have a tuple (Value?, Error?)

, where Value

is the type that the task computes. But instead you could use the more convenient Swift Enum for this, for example. Result<T>

or Try<T>

(you may need to search the internet).

Then you use it like this:

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:

  • If a function calls an internal async function, it inevitably becomes an async function. *)

  • An asynchronous function must have a completion handler (otherwise it's kind of "fire and forget").

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

  • The completion handler must be called asynchronously (relative to the caller)

  • The completion handler should be called in a private executable context (aka dispatch queue), unless the function has a parameter specifying where to execute the completion handler. Never use the main thread or the main dispatch queue - unless you explicitly state this fact in the docs or are willing to risk deadlocks.

*) You can force it to make it synchronous by using semaphores that block the calling thread. But this is ineffective and very rarely necessary.

Okay, you can conclude that this looks a bit cumbersome. Fortunately, it helps there - you can search for Future

or Promise

that can wrap this nicely and make the code more concise and understandable.

Note. In Unit Test, you will use waits to handle asynchronous calls (see the XCTest framework).

0


source







All Articles