Microfutures 0.1.2

Microfutures 0.1.2

测试已测试
Lang语言 SwiftSwift
许可协议 MIT
发布最后发布日期2017年1月
SwiftSwift 版本3.0
SPM支持 SPM

Fernando Ortiz 维护。



  • 作者
  • Fernando Ortiz

Microfutures

简介

Microfutures 是一个非常小的库(60 行代码),实现了简单的 Futures/Promises 流程。它还拥有与 RxSwift 相似的目标接口。

什么是 Future?

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 简单得不能更简单。

让我们比较您如何编写基于回调的异步函数与基于 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 的值

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
    }

使用flatMap串联 Futures

有时,你需要在另一个异步函数之后执行一个异步函数。这通常会导致回调地狱。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

最后一步是订阅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是一个函数,它接收一个错误并处理它。

与RxSwift的相似性

Future方法名称是按照RxSwift的名称来选择的。mapflatMapsubscribe是RxSwift使用的名称,这个库可以作为学习RxSwift术语的入门。

示例

要运行示例项目,请先克隆仓库,然后从Example目录运行pod install

安装

Microfutures通过CocoaPods提供。要安装它,只需将以下行添加到您的Podfile中

pod "Microfutures"

或者,您可以直接将从项目中复制粘贴Microfutures.swift

作者

Fernando Ortiz, [email protected]

许可

Microfutures可在MIT许可下使用。有关更多信息,请参阅LICENSE文件。