如果你只是想处理回调地狱 callback hell ,那么 PromiseKit 更适合你,使用 RxSwift 反而会更加麻烦。

如题,本文将讨论我理解的 RxSwift 和 PromiseKit 有什么不同。并解释为什么 RxSwift 不适合仅用来处理异步

在写本文之前我特地先咨询了一下臧老师 ,回答如下。

我主要是想空手套技术文,很可惜,基本没成功。

本文基于 Swift 3 & Xcode 8 beta 6 。


通常我们都是因为遇到回调地狱才了解到 RxSwift / RAC 的,但经过我不断实践,得出一个结论,如果只想考虑如何更好的处理回调地狱,PromiseKit 则是更好的选择。我将在本文中介绍 PromiseKit 的基本使用,并解释为什么 PromiseKit 是优选,并给出一些什么时候应当考虑 RxSwift 的场景。在开始之前,我想先再次强调 Reactive Extensions 只是一个思想

PromiseKit

创建一个 Promise

PromiseKit 的文档并不是很容易理解,因为想要找一下如何创建一个 Promise 就很麻烦,至少在首页中和 README 中看不到相关说明。

吐槽一点,这官网竟然放广告。

但创建一个 Promise 并不麻烦,使用其初始化方法即可。比如创建一个带网络请求的 Promise 如下。

1
2
3
4
5
6
7
8
9
10
11
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 解析。

1
2
3
4
5
6
7
8
9
10
.then { (data) in
Promise<Any> { (fulfill, error) in
do {
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
fulfill(json)
} catch {
fulfill(error)
}
}
}

再去打印 JSON 结果。

1
2
3
.then { (json) in
print(json)
}

那么问题来了,如何执行这段代码
添加一个方法/函数,调用即可。完整代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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)
}
}

使用 PromiseKit 解决回调地狱

在上面的代码中可以看到,如果我们需要连续调用多个 API ,写成像上面这样的链式调用是很有必要的,这极大地提高的代码的可读性,即解决了回调地狱问题。
但还有一个问题,只能通过调用 fetchData 执行这段代码吗?我们先来看如何使用 RxSwift 解决上述的回调地狱问题。

使用 RxSwift 解决回调地狱

我们也可以以类似上述代码的形式完成需求。

1
2
3
4
5
6
7
8
9
10
11
12
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 ,然后调用该方法进行网络请求,但这种解法并不优雅。

再来思考一个问题,什么时候调用该方法?

  • 点击 Button
  • 下拉刷新

于是我们需要在这些地方写上 fetchData() 获取结果。当然我们还有一种更好的写法。找到逻辑源头是解决问题的关键,比如这里触发 fetchData() 的原因是 Button 点击的 Target 事件,我们可以从这个逻辑源头写起。

1
2
3
button.rx.tap
.flatMap { URLSession.shared.rx.data(URLRequest(url: URL(string: "https://httpbin.org/get?foo=bar")!)) }
// ...

RxSwift vs PromiseKit

我们可以看到二者相类似的地方是都能链式处理异步,不同点是 RxSwift 提供了声明式的写法,而 PromiseKit 仍然需要命令式的写法,以调用方法的形式执行代码。RxSwift 让我们更专注于做什么,即指出实际的逻辑场景

还有一点不同,我们用代码说明。

1
2
3
4
5
6
7
Promise<Int> { fulfill, reject in
fulfill(1)
fulfill(2)
}
.then { (value) in
print(value)
}

打印结果为。

1
1
1
2
3
4
5
6
7
8
9
10
Observable<Int>
.create { (observer) in
observer.onNext(1)
observer.onNext(2)
observer.onCompleted()
return Disposables.create()
}
.subscribe(onNext: { value in
print(value)
})

打印结果为。

1
2
1
2

即 PromiseKit 不具备流的特性,即不支持依赖时间顺序依次传递值,换句话说就是调用闭包 fulfill 多次也只能执行一次。这就没办法让我们以完整的声明式的写法完成需求。

仅需要处理回调地狱,选择 PromiseKit

PromiseKit 的上手难度相比 RxSwift 要低很多,每个方法的含义都比较清晰,比如 then always when 等方法,可以清晰的表达方法的含义。

  • then 表示获取到值后,接下来做什么?
  • always 不管是获取到值还是出现 error ,都要执行 always 中的代码
  • when 当几个 promise 都获取到结果时,做什么?

而对应 PromiseKit 中的 when ,可以是 map flatMap doOnNext 等方法,概念理解相比 PromiseKit 要辛苦很多。