ResultPromises
帮助组织异步调用,形成功能单子。
它允许替换不太令人愉快的异步完成块,例如
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'
使用
问题
苹果提出的Swift错误处理建议在必要时抛出错误。然而,许多API无法抛出错误,此外,异步块还不能捕获错误。异步块链,其中每个块都可以抛出不同的错误,成为了一场噩梦。
例如,如果您需要从远程URL获取一些数据,解析到模型并在屏幕上显示,否则显示错误。
结果
结果 是一种泛型枚举,可以将任何类型包装起来,以便与可能的错误统一,而不是抛出错误。可以用来表示某个块执行的成功或失败,返回一个错误。
结果 可以通过调用 .resolve
方法转换回可抛出的函数闭包。它将返回预期的类型或抛出一个错误。
结果 可以通过 .map
或 .flatMap
连接和转换到另一个 结果 类型。
承诺
承诺是一个泛型包装对象,用于在某个同步或异步事件上安排延迟触发。可以在创建异步块之前创建承诺,并在该块内部将其解析为结果或错误。承诺是引用类型,因此同一个承诺可以在多个函数中使用。
可以通过实例化它简单地创建承诺
let promiseString = Promise<String>()
当承诺被创建时,它的解析状态是未定义的。可以通过提供承诺的结果来将其解析为成功状态,或者通过带有一个错误的失败状态。
promiseStringA.resolve(result: testString)
promiseStringB.resolve(error: TestError.test)
承诺只能被解析一次。尝试解析已解析的承诺不会改变其状态,也不会触发任何解析处理程序。但是,必须在控制台记录警告信息。
可以订阅多个解析处理程序:onSuccess、onError和onComplete。必须在解析之前将解析处理程序分配给承诺。它们将触发承诺解析时的情况。一个承诺可以有多个不同类型的解析处理程序。
*** 解析处理程序将在它们被分配的队列上执行 *** 这可能会引入轻微的执行延迟。
当承诺解析为成功或错误状态时,将触发onSuccess和onError处理程序。onComplete将在任何情况下都会被触发。
示例中,在网络请求处理过程中非常有用。当有数据可用时(成功),UI 需要使用新数据刷新;否则,需要显示错误信息(错误)。在所有情况下,都需要隐藏活动指示器(完成)。
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 方法轻松地将相关的异步命令链组合在一起。然后使用提供的闭包创建新的 Promise(隐式或显式依赖于闭包的结果类型)。
如果发生错误,所有后续的 Promise 将同时解析到错误状态。这允许为整个依赖的异步操作链提供单个错误处理器。
它作为参数接受三种可能的闭包之一
.then {(previousPromiseOutput) -> Promise<Type> in ... }
允许创建新的异步 Promise,可以异步解析为 Type 或某些 Error。.then {(previousPromiseOutput) -> Result<Type> in ... }
允许返回包裹在 Type 或 Error 中的 Result 类型.then { (previousPromiseOutput) -> [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, [email protected]
许可协议
ResultPromises 在 MIT 许可证下可用。请参阅 LICENSE 文件以获取更多信息。