RxSwift vs PromiseKit

如果你只是想处理回调地狱 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

# 创建一个 Promise

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)
    }
}

# 使用 PromiseKit 解决回调地狱

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

# 使用 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 ,然后调用该方法进行网络请求,但这种解法并不优雅。

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

  • 点击 Button
  • 下拉刷新

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

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

# RxSwift vs PromiseKit

我们可以看到二者相类似的地方是都能链式处理异步,不同点是 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

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

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

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