有没有办法在 Swift 3 中从异步闭包中抛出错误?

Ale*_*een 6 asynchronous grand-central-dispatch swift swift3

我正在使用DispatchQueue这样的异步方式在测试中执行一些函数:

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()
}
Run Code Online (Sandbox Code Playgroud)

有时do块内的代码可能会抛出错误,我必须稍后才能发现。由于我正在开发一个测试,如果do块内的代码抛出错误,我希望它失败。有没有办法抛出错误,而不会在queue.async调用中捕获它?

Cou*_*per 6

您不能抛出错误,但可以返回错误:

首先,您还需要使调用函数异步:

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

完成处理程序采用一个参数,我们可以说它是一个包含值或错误的“结果”。在这里,我们有一个元组(Value?, Error?),其中Value是任务计算的类型。但相反,您可以利用更方便的 Swift Enum 来实现此目的,例如Result<T>or Try<T>(您可能想要搜索网络)。

然后,您可以按如下方式使用它:

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
    ...
}
Run Code Online (Sandbox Code Playgroud)

一些可能有帮助的规则:

  1. 如果一个函数在内部调用一个异步函数,那么它也不可避免地成为一个异步函数。*)

  2. 异步函数应该有一个完成处理程序(否则,它是某种“即发即忘”)。

  3. 无论如何,必须调用完成处理程序。

  4. 完成处理程序必须异步调用(尊重调用者)

  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).