帮助以函数式 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 文件:
github "Michael-Vorontsov/ResultPromises"
pod ‘ResultPromises’, :git => 'https://github.com/Michael-Vorontsov/ResultPromises.git'
Apple 提出的 Swift 错误处理建议在必要时抛出错误。然而,许多 API 无法抛出错误,而且异步块还不能捕获错误。异步块链,其中每个块都可能抛出不同的错误,这变成了一场噩梦。
例如,如果您必须从远程 URL 获取一些数据,解析为 Model 并显示在屏幕上,否则显示错误。
Result 是一个泛型枚举,可以包装任何类型,以将其与可能的错误统一起来,而不是抛出它。 可以用来指示某些块的执行成功,或以任何错误作为单一返回值的失败。
Result 可以通过调用 .resolve
方法转换回可抛出闭包或函数。它将返回预期的类型或抛出一个错误。
可以使用 .map
或 .flatMap
将 Result 链接并转换为另一个 Result 类型。
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 将立即解析为错误状态。 这允许为整个相互依赖的异步操作链提供单个错误处理程序。
它接受作为参数的三个可能的闭包之一:
.then {(previousePromiseOutput) -> Promise<Type> in ... }
允许创建一个新的异步 promise,该 promise 可以异步解析为 *Type* 或一些 *Error*。.then {(previousePromiseOutput) -> Result<Type> in ... }
允许返回包装在 *Type* 或 *Error* 中的 Result 类型。.then { (previousePromiseOutput) -> [Type] in ... }
允许直接返回 *Type* 对象或抛出 *Error*。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 文件。