测试已测试 | ✗ |
Lang语言 | SwiftSwift |
许可协议 | MIT |
发布最后发布日期 | 2017年1月 |
SwiftSwift 版本 | 3.0 |
SPM支持 SPM | ✗ |
由 Fernando Ortiz 维护。
Microfutures 是一个非常小的库(60 行代码),实现了简单的 Futures/Promises 流程。它还拥有与 RxSwift 相似的目标接口。
Future 是对尚未生成的值的表示。Futures 的最佳用途是简化异步流程。您不必编写嵌套回调,可以串联 Futures,将那可怕的回调地狱转变为美丽的功能流水线。
Microfutures 允许您将以下结构转换为以下结构
getUser(withID: 3) { user, error in
if let error = error {
print("An error ocurred")
} else if let user = user {
self.getPosts(forUserID: user.id) { posts, error in
if let error = error {
print("An error ocurred")
} else if let posts = posts {
if let firstPost = posts.first {
self.getComments(forPostID: firstPost.id) { error, comments in
if let error = error {
print("An error ocurred")
} else if let comments = comments {
print("Comments count: \(comments.count)")
}
}
}
}
}
}
}
(在这里,我没有夸大,这是一个非常常见的场景,每个人至少都一次写过类似的东西。)
这是更清晰的。
getUser(withID: 3)
.flatMap(getPosts)
.map { posts in return posts.first?.id }
.flatMap(getComments)
.map { comments in return comments.count }
.subscribe (
onNext: { commentsCount in
print("Comments count: \(commentsCount)")
},
onError: { error in
print("An error ocurred.")
}
)
创建 Future 简单得不能更简单。
让我们比较您如何编写基于回调的异步函数与基于 Future 的异步函数。
// Callback based func:
func getUser(withID id: Int, completion: (Error?, User?) -> Void) {
APIClient
// This api client also works using callbacks
.get("https://somecoolapi.com/users/\(id)") { error, json in
if let error = error {
completion(error, nil)
return
} else {
guard let json = json else {
completion(NetworkingError.emptyResponse, nil)
return
}
guard let user = User(json: json) else {
completion(NetworkingError.invalidReponse, nil)
return
}
completion(nil, user)
}
}
}
以下是基于 Future 的异步函数示例
func getUser(withID id: Int) -> Future<User> {
return Future { completion in
APIClient
// Imagine that this api client still uses a callback based approach.
.get("https://somecoolapi.com/users/\(id)") { error, json in
if let error = error {
completion(.failure(error))
return
} else {
guard let json = json else {
completion(.failure(NetworkingError.emptyResponse))
return
}
guard let user = User(json: json) else {
completion(.failure(NetworkingError.invalidReponse))
return
}
completion(.success(user))
}
}
}
}
是的,它完全一样。但是,这是因为我们的 API 客户端仍然采用的是基于回调的方法。
使用基于 Future 的方法的优点之一是您可以使用功能工具。将前面的片段与以下内容进行比较
func getUser(withID id: Int) -> Future<User> {
return APIClient
.get("https://somecoolapi.com/users/\(id)")
.map { json in
guard let user = User(json: json) else {
throw NetworkingError.invalidReponse
}
completion(.success(user))
}
}
正如我将稍后解释的,map
函数将 Future
值转换成另一个值。在本例中,map
将 json 值转换为用户对象。
map
将 Future 的输出转换为另一个值。
例如
getAlbum(withID: 3)
.map { album in
return album.title
}
.map { albumTitle in
return "THe album title is \(albumTitle)"
}
// ...
关于 map
的一个重要的事是,如果 Future 包含错误,则它不会执行。
map
在必要时也可以抛出错误。
getUser(withID: 3)
.map { user in
guard let mobile = user.mobilePhone else {
throw UserError.noMobileNumber
}
return mobile
}
有时,你需要在另一个异步函数之后执行一个异步函数。这通常会导致回调地狱。Futures提供了解决方案,那就是使用flatMap
flatMap
接收一个函数,该函数将future的输出值进行转换并返回另一个future。
例如
func getPosts(forUserID userID: Int) -> Future<[Post]> {
return APIClient
.get("https://somecoolapi.com/posts?userID=\(userID)")
.map { json in
guard let jsonArray = json as? [JSON] else {
throw NetworkingError.invalidReponse
}
return jsonArray.map(Post.init)
}
}
getUser(withID: 3)
.flatMap { user in
return getPosts(forUserID: user.id)
}
// Or doing this
getUser(withID: 3)
.map { user in return user.id }
.flatMap (getPosts)
最后一步是订阅Future
。这是通过使用subscribe
方法实现的。
例如
getUser(withID: 3)
.map { user in return user.id }
.flatMap (getPosts)
.subscribe(
onNext: { posts in
// Do something with posts...
},
onError: { error in
// Handle this error.
}
)
订阅接收两个函数,一个用于成功路径,另一个用于错误情况。
onNext
是一个函数,它接收Future
值并使用该值执行某些操作。
onError
是一个函数,它接收一个错误并处理它。
Future
方法名称是按照RxSwift的名称来选择的。map
、flatMap
和subscribe
是RxSwift使用的名称,这个库可以作为学习RxSwift术语的入门。
要运行示例项目,请先克隆仓库,然后从Example目录运行pod install
。
Microfutures通过CocoaPods提供。要安装它,只需将以下行添加到您的Podfile中
pod "Microfutures"
或者,您可以直接将从项目中复制粘贴Microfutures.swift
。
Fernando Ortiz, [email protected]
Microfutures可在MIT许可下使用。有关更多信息,请参阅LICENSE文件。