如果你只是想处理回调地狱 callback hell ,那么 PromiseKit 更适合你,使用 RxSwift 反而会更加麻烦。
如题,本文将讨论我理解的 RxSwift 和 PromiseKit 有什么不同。并解释为什么 RxSwift 不适合仅用来处理异步。
在写本文之前我特地先咨询了一下臧老师 (opens new window) ,回答如下。
我主要是想空手套技术文,很可惜,基本没成功。
本文基于 Swift 3 & Xcode 8 beta 6 。
通常我们都是因为遇到回调地狱才了解到 RxSwift / RAC 的,但经过我不断实践,得出一个结论,如果只想考虑如何更好的处理回调地狱,PromiseKit 则是更好的选择。我将在本文中介绍 PromiseKit 的基本使用,并解释为什么 PromiseKit 是优选,并给出一些什么时候应当考虑 RxSwift 的场景。在开始之前,我想先再次强调 Reactive Extensions 只是一个思想。
PromiseKit 的文档 (opens new window)并不是很容易理解,因为想要找一下如何创建一个 Promise
就很麻烦,至少在首页中和 README 中看不到相关说明。
吐槽一点,这官网竟然放广告。
但创建一个 Promise 并不麻烦,使用其初始化方法即可。比如创建一个带网络请求的 Promise 如下。
Promise<Data> { (fulfill, reject) in
URLSession.shared
.dataTask(with: URL(string: "https://httpbin.org/get?foo=bar")!) { (data, _, error) in
if let data = data {
fulfill(data)
} else if let error = error {
reject(error)
}
}
.resume()
}
此后我们便可以使用 then
等方法处理异步回调的结果,还可以将结果继续传递下去。比如做一步 JSON 解析。
.then { (data) in
Promise<Any> { (fulfill, error) in
do {
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
fulfill(json)
} catch {
fulfill(error)
}
}
}
再去打印 JSON 结果。
.then { (json) in
print(json)
}
那么问题来了,如何执行这段代码? 添加一个方法/函数,调用即可。完整代码如下。
func fetchData() {
Promise<Data> { (fulfill, reject) in
URLSession.shared
.dataTask(with: URL(string: "https://httpbin.org/get?foo=bar")!) { (data, _, error) in
if let data = data {
fulfill(data)
} else if let error = error {
reject(error)
}
}
.resume()
}
.then { (data) in
Promise<Any> { (fulfill, error) in
do {
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
fulfill(json)
} catch {
fulfill(error)
}
}
}
.then { (json) in
print(json)
}
}
在上面的代码中可以看到,如果我们需要连续调用多个 API ,写成像上面这样的链式调用是很有必要的,这极大地提高的代码的可读性,即解决了回调地狱问题。
但还有一个问题,只能通过调用 fetchData
执行这段代码吗?我们先来看如何使用 RxSwift 解决上述的回调地狱问题。
我们也可以以类似上述代码的形式完成需求。
URLSession.shared.rx
.data(URLRequest(url: URL(string: "https://httpbin.org/get?foo=bar")!))
.map {
try JSONSerialization.jsonObject(with: $0, options: .allowFragments)
}
.subscribe(onNext: { (json) in
print(json)
}, onCompleted: {
print("Completed")
}, onDisposed: {
print("Disposed")
})
那么如何执行这段代码?
我们仍然可以选择写一个 fetchData
,然后调用该方法进行网络请求,但这种解法并不优雅。
再来思考一个问题,什么时候调用该方法?
于是我们需要在这些地方写上 fetchData()
获取结果。当然我们还有一种更好的写法。找到逻辑源头是解决问题的关键,比如这里触发 fetchData()
的原因是 Button
点击的 Target 事件,我们可以从这个逻辑源头写起。
button.rx.tap
.flatMap { URLSession.shared.rx.data(URLRequest(url: URL(string: "https://httpbin.org/get?foo=bar")!)) }
// ...
我们可以看到二者相类似的地方是都能链式处理异步,不同点是 RxSwift 提供了声明式的写法,而 PromiseKit 仍然需要命令式的写法,以调用方法的形式执行代码。RxSwift 让我们更专注于做什么,即指出实际的逻辑场景。
还有一点不同,我们用代码说明。
Promise<Int> { fulfill, reject in
fulfill(1)
fulfill(2)
}
.then { (value) in
print(value)
}
打印结果为。
1
Observable<Int>
.create { (observer) in
observer.onNext(1)
observer.onNext(2)
observer.onCompleted()
return Disposables.create()
}
.subscribe(onNext: { value in
print(value)
})
打印结果为。
1
2
即 PromiseKit 不具备流的特性,即不支持依赖时间顺序依次传递值,换句话说就是调用闭包 fulfill
多次也只能执行一次。这就没办法让我们以完整的声明式的写法完成需求。
PromiseKit 的上手难度相比 RxSwift 要低很多,每个方法的含义都比较清晰,比如 then
always
when
等方法,可以清晰的表达方法的含义。
then
表示获取到值后,接下来做什么?always
不管是获取到值还是出现 error ,都要执行 always
中的代码when
当几个 promise 都获取到结果时,做什么?而对应 PromiseKit 中的 when
,可以是 map
flatMap
doOnNext
等方法,概念理解相比 PromiseKit 要辛苦很多。