Header

ResultPromises

Travice Badge

帮助以函数式 Monad 的形式组织异步调用。

它允许替换不太令人愉快的异步完成块,例如:

let request = URLRequest(url: URL(string: "https://jsonplaceholder.typicode.com/users")!)
session.dataTask(with: request) { (data, response, error) in
  DispatchQueue.main.async {
    self.tableView.refreshControl?.endRefreshing()
  }
  if let error = error {
    self.showError(message: error.localizedDescription)
    return
  }

  let httpResponse = response as! HTTPURLResponse

  guard (200...299).contains( httpResponse.statusCode) else {
    self.showError(message: "Code: \(httpResponse.statusCode)")
    return
    }

  guard let data = data else {
    self.showError(message: "Data is Empty!")
    return
    }

  let decoder = JSONDecoder()
  do {
    let users =  try decoder.decode([User].self, from: data)
    self.users = users
    DispatchQueue.main.async {
      self.tableView.reloadData()
    }
  } catch {
    self.showError(message: error.localizedDescription)
  }
}.resume()

为非常直接的序列:

URLRequest.requestFor(path: "https://jsonplaceholder.typicode.com/users")
  .then {(request) -> Promise<Data> in
    return self.session.fetchData(from: request)
  }
  .then { (data) -> [User] in
    let decoder = JSONDecoder()
    return try decoder.decode([User].self, from: data)
  }
  .onComplete { (_) in
    self.tableView.refreshControl?.endRefreshing()
  }
  .onSuccess { (users) in
    self.users = users
    self.tableView.reloadData()
  }
  .onError { (error) in
    self.showError(message: error.localizedDescription)
  }

甚至可以一次性请求并转换为通用的可编码对象:

URLRequest.requestFor(path: "https://jsonplaceholder.typicode.com/users")
  .then {(request) -> Promise<[User]> in
    return self.session.fetchRESTObject(from: request)
  }
  .onComplete { (_) in
    self.tableView.refreshControl?.endRefreshing()
  }
  .onSuccess { (users) in
    self.users = users
    self.tableView.reloadData()
  }
  .onError { (error) in
    self.showError(message: error.localizedDescription)
  }
}

安装

Carthage

添加到 Carthage 文件:

github "Michael-Vorontsov/ResultPromises"

CocoaPods

pod ‘ResultPromises’, :git => 'https://github.com/Michael-Vorontsov/ResultPromises.git'

使用

问题

Apple 提出的 Swift 错误处理建议在必要时抛出错误。然而,许多 API 无法抛出错误,而且异步块还不能捕获错误。异步块链,其中每个块都可能抛出不同的错误,这变成了一场噩梦。

例如,如果您必须从远程 URL 获取一些数据,解析为 Model 并显示在屏幕上,否则显示错误。

Result

Result 是一个泛型枚举,可以包装任何类型,以将其与可能的错误统一起来,而不是抛出它。 可以用来指示某些块的执行成功,或以任何错误作为单一返回值的失败。

Result 可以通过调用 .resolve 方法转换回可抛出闭包或函数。它将返回预期的类型或抛出一个错误。

可以使用 .map.flatMapResult 链接并转换为另一个 Result 类型。

Promise

Promise 是一个泛型包装对象,用于在某些同步或异步事件上调度延迟触发器。 Promise 可以在一些异步块之前创建,并在该块内解析为结果或错误。 Promise 是引用类型,因此一个 Promise 可以跨多个函数使用。

可以通过简单地实例化来创建 Promise:

let promiseString = Promise<String>()

当 Promise 被创建时,它的解析状态是未定义的。 Promise 可以解析为成功状态,提供结果,或者解析为失败状态,并提供一些错误。

promiseStringA.resolve(result: testString)
promiseStringB.resolve(error: TestError.test)

Promise 只能被解析一次。 额外尝试解析 Promise 不会改变它的状态,也不会触发任何解析处理程序。 但会记录警告消息到控制台。

Promise 可以订阅多个解析处理程序: onSuccess、onError 和 onComplete。 解析处理程序必须在 Promise 被解析之前分配给 Promise。 无论何时 Promise 被解析,它们都会被触发。 一个 Promise 可以有任意类型的多个解析处理程序。

*** 解析处理程序将在分配它们的队列上执行 *** 这可能会引入轻微的执行延迟。

当 Promise 解析为成功或错误状态时,将触发 onSuccess 和 onError 处理程序。 无论如何都会触发 onComplete。

例如,这在处理网络请求时很有用。 当数据可用时,必须使用新数据刷新 UI(onSuccess)。 否则必须显示错误消息(onError)。 无论如何都必须隐藏活动指示器(onComplete)。

promiseStrings.
  .onComplete { (_) in
    self.tableView.refreshControl?.endRefreshing()
  }
  .onSuccess { ([loadedStrings]) in
    self.strings = loadedStrings
    self.tableView.reloadData()
    }
  .onError { (error) in
    self.showError(message: error.localizedDescription)
  }

可以使用 then 方法轻松地将相关的异步命令链连接在一起。 Then 将使用提供的闭包(隐式或显式取决于闭包结果类型)创建新的 promise。

如果发生错误,所有后续的 promise 将立即解析为错误状态。 这允许为整个相互依赖的异步操作链提供单个错误处理程序。

它接受作为参数的三个可能的闭包之一:

urlRequestPromise
  .then {(request) -> Promise<Data> in
    // New promise going to be created here.
    // Code bellow will be triggered when request had been created
    let promise = Promise<(Data?, HTTPURLResponse)>()
    self.dataTask(with: request) { (data, response, error) in
    // Can be thrown out of here, so in case of error
    // resolve created promise to error state
    guard error == nil else {
      promise.resolve(error: NetworkError.network(error: error))
      return
    }
    guard let data = data, data.count > 0 else {
      promise.resolve(error: NetworkError.missedData)
      return
    }
    //Data no empty here for sure
    promise.resolve(success: data)
  }
  .then { (data) -> [User] in
    // It will be triggered when data arrived
    // And only if data available.
    // If original urlRequestPromise resolved to error, or error happened during network
    // this Promise will be resolved to error at once without executing this block.
    let decoder = JSONDecoder()
    return try decoder.decode([User].self, from: data)
    // JSONDecoder can throw. Promise will catch it and resolve itself into error.
  }

与解析处理程序不同,then 闭包可以在 Promise 解析发生的线程上执行。 这意味着随后的 then 闭包可能比解析处理程序更早执行。

请注意,解析处理程序可以与 then 混合使用,例如:

urlRequestPromise
  .onError{ _ in
    print("Error while creating URL request. No network request happend at all!")
  }
  .then {(request) -> Promise<Data> in
    /* Load data */
  }
  .onSuccess{ data in
    print("\(data.count) bytes had been received")
  }
  .onComplete{ _ in
    /* Stop activity indicator */
  }
  .then { (data) -> ModelData in
    /* Parse data */
  }
  .onSuccess( modelData in
    /* refresh UI */
  }

扩展

提供了简单的 URLSession 和 URLRequest 扩展。 代码中提供了丰富的注释。 请看看。

请查看源代码、单元测试和示例以获取更多详细信息。

示例项目

可在 Example/iOS 文件夹中找到。

作者

Michael Vorontsov, michel06@ukr.net

许可证

ResultPromises 在 MIT 许可证下可用。 有关其他详细信息,请参阅 LICENSE 文件。