独立博客 ZYF.IMhtml
在 GitHub Trending 中老是看到 mxcl/PromiseKit 它是主要解决的是 “回调地狱” 的问题,决定尝试用一下。git
环境:Swift 4.二、PromiseKit 6github
下面是一个典型的 promise 链式(chain)调用:编程
firstly {
login()
}.then { creds in
fetch(avatar: creds.user)
}.done { image in
self.imageView = image
}
复制代码
若是这段代码使用完成回调(completion handler
)实现,他将是:json
login { creds, error in
if let creds = creds {
fetch(avatar: creds.user) { image, error in
if let image = image {
self.imageView = image
}
}
}
}
复制代码
then
是完成回调的另外一种方式,可是它更丰富。在处级阶段的理解,它更具备可读性。上面的 promise chain 更容易阅读和理解:一个异步操做接着另外一个,一行接一行。它与程序代码很是接近,由于咱们很容易获得 Swift 的当前状态。swift
done
与 then
基本是同样的,可是它将再也不返回 promise。它是典型的在末尾 “success” 部分的 chain。在上面的例子 done
中,咱们接收到了最终的图片并使用它设置了 UI。数组
让咱们对比一下两个 login
的方法签名:promise
// Promise:
func login() -> Promise<Creds>
// Compared with:
func login(completion: (Creds?, Error?) -> Void)
// 可选型,二者都是可选
复制代码
区别在于 promise,方法返回 promises 而不是的接受和运行回调。每个处理器(handler)都会返回一个 promise。Promise 对象们定义 then
方法,该方法在继续链式调用以前等待 promise 的完成。chains 在程序上解决,一次一个 promise。bash
Promise 表明将来异步方法的输入值。它有一个表示它包装的对象类型的类型。例如,在上面的例子里,login
的返回 Promise 值表明一个 Creds 的一个实例。闭包
能够注意到这与 completion pattern 的不一样,promises chain 彷佛忽略错误。并非这样,实际上:promise chain 使错误处理更容易访问(accessible),并使错误更难被忽略。
有了 promises,错误在 promise chain 上级联(cascade along),确保你的应用的健壮(robust)和清晰的代码。
firstly {
login()
}.then { creds in
fetch(avatar: creds.user)
}.done { image in
self.imageView = image
}.catch {
// 整个 chain 上的错误都到了这里
}
复制代码
若是你忘记了 catch 这个 chain,Swift 会发出警告
每一个 promise 都是一个表示单个(individual)异步任务的对象。若是任务失败,它的 promise 将成为 rejected
。产生 rejected
promises 将跳事后面全部的 then
,而是将执行 catch
。(严格上说是执行后续全部的 catch
处理)
这与 completion handler 对比:
func handle(error: Error) {
//...
}
login { creds, error in
guard let creds = creds else { return handle(error: error!) }
fetch(avatar: creds.user) { image, error in
guard let image = image else { return handle(error: error!) }
self.imageView.image = image
}
}
复制代码
使用 guard
和合并错误对处理有所保证,可是 promise chain 更具备可读性。
firstly {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
return login()
}.then {
fetch(avatar: $0.user)
}.done {
self.imageView = $0
}.ensure {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}.catch {
// ...
}
复制代码
不管在 chain 哪里结束,成功或者失败,ensure
终将被执行。也可使用 finally
来完成相同的事情,区别是没有返回值。
spinner(visible: true)
firstly {
foo()
}.done {
// ...
}.catch {
// ...
}.finally {
self.spinner(visible: false)
}
复制代码
多个异步操做同时处理时可能又难又慢。例如当 操做1
和 操做2
都完成时再返回结果:
// 串行操做
operation1 { result1 in
operation2 { result2 in
finish(result1, result2)
}
}
复制代码
// 并行操做
var result1: ...!
var result2: ...!
let group = DispatchGroup()
group.enter()
group.enter()
operation1 {
result1 = $0
group.leave()
}
operation2 {
result2 = $0
group.leave()
}
group.notify(queue: .main) {
finish(result1, result2)
}
复制代码
令人 Promises 将变得容易不少:
firstly {
when(fulfilled: operation1(), operation2())
}.done { result1, result2 in
// ...
}
复制代码
when
等待全部的完成再返回 promises 结果。
PromiseKit 提过了一些 Apple API 的扩展,例如:
firstly {
CLLocationManager.promise()
}.then { location in
CLGeocoder.reverseGeocode(location)
}.done { placemarks in
self.placemark.text = "\(placemarks.first)"
}
复制代码
同时须要指定 subspaces:
pod "PromiseKit"
pod "PromiseKit/CoreLocation"
pod "PromiseKit/MapKit"
复制代码
更多的扩展能够查询 PromiseKit organization,甚至扩展了 Alamofire 这样的公共库。
有时你的 chains 仍然须要以本身开始,或许你使用的三方库没有提供 promises 或者本身写了异步系统,不要紧,他们很是容易添加 promises。若是你查看了 PromiseKit 的标准扩展,能够看到使用了下面相同的描述:
已有代码:
func fetch(completion: (String?, Error?) -> Void)
复制代码
转换:
func fetch() -> Promise<String> {
return Promise { fetch(completion: $0.resolve) }
}
复制代码
更具备可读性的:
func fetch() -> Promise<String> {
return Promise { seal in
fetch { result, error in
seal.resolve(result, error)
}
}
}
复制代码
Promise 初始化程序提供的 seal
对象定义了不少处理 garden-variety
完成回调的方法。
PromiseKit 设置尝试以
Promise(fetch)
进行处理,可是完成经过编译器的消歧义。
从 PromiseKit 5 开始,提供了 Guarantee 以作补充,目的是完善 Swift 强的的异常处理。
Guarantee
永远不会失败,因此不能被 rejected
。
firstly {
after(seconds: 0.1)
}.done {
// 这里不要加 catch
}
复制代码
Guarantee
的语法相较更简单:
func fetch() -> Promise<String> {
return Guarantee { seal in
fetch { result in
seal(result)
}
}
}
// 减小为
func fetch() -> Promise<String> {
return Guarantee(resolver: fetch)
}
复制代码
then
要求返回另外一个 promisemap
要求返回一个 object 或 value 类型compactMap
要求返回一个 可选型,如过返回 nil
,chain 将失败并报错 PMKError.compactMap
firstly {
URLSession.shared.dataTask(.promise, with: rq)
}.compactMap {
try JSONSerialization.jsonObject($0.data) as? [String]
}.done { arrayOfStrings in
// ...
}.catch { error in
// Foundation.JSONError if JSON was badly formed
// PMKError.compactMap if JSON was of different type
}
复制代码
除此以外还有:thenMap
compactMapValues
firstValue
etc
get
会获得 done
中相同值。
firstly {
foo()
}.get { foo in
// ...
}.done { foo in
// same foo!
}
复制代码
为 debug 提供 tap
,与 get
相似可是能够获得 Result<T>
这样就能够检查 chain 上的值:
firstly {
foo()
}.tap {
print($0)
}.done {
// ...
}.catch {
// ...
}
复制代码
上面例子中的 firstly
是语法糖,非必须可是可让 chains 更有可读性。
firstly {
login()
}.then { creds in
// ...
}
// 也能够
login().then { creds in
// ...
}
复制代码
知识点:login()
返回了一个 Promise
,同时全部的 Promise
有一个 then
方法。firstly
返回一个 Promise
,一样 then
也返回一个 Promise
。
when(fulfilled:)
在全部异步操做执行完后才执行回调,一个失败 chain 将 rejects。It's important to note that all promises in the when continue. Promises have no control over the tasks they represent. Promises are just wrappers around tasks.when(resolved:)
使一个或多个组件承诺失败也会等待。此变体 when
生成的值是 Result<T>
的数组,全部要保证相同的泛型。race
只要有一个异步操做执行完毕,就马上执行 then
回调。其它没有执行完毕的异步操做仍然会继续执行,而不是中止。Swift 有自动推断返回值和单行返回。
foo.then {
bar($0)
}
// is the same as:
foo.then { baz -> Promise<String> in
return bar(baz)
}
复制代码
这样有好有坏,具体能够查询 Troubleshooting
Reference:
-- EOF --