全部文章
以下是对
PromiseKit
的README.md
的翻译。
错误的使用
多重包含 Promise
错误的示例:
func toggleNetworkSpinnerWithPromise<T>(funcToCall: () -> Promise<T>) -> Promise<T> {
return Promise { seal in
firstly {
setNetworkActivityIndicatorVisible(true)
return funcToCall()
}.then { result in
seal.fulfill(result)
}.always {
setNetworkActivityIndicatorVisible(false)
}.catch { err in
seal.reject(err)
}
}
}
修改为:
func toggleNetworkSpinnerWithPromise<T>(funcToCall: () -> Promise<T>) -> Promise<T> {
return firstly {
setNetworkActivityIndicatorVisible(true)
return funcToCall()
}.always {
setNetworkActivityIndicatorVisible(false)
}
}
在已有的 promise 上,不需要再使用一个 promise 去包裹它。
Promise 中的可选
当你看到 Promise<Item?>
时,很有可能用错了 Promise。比如:
return firstly {
getItems()
}.then { items -> Promise<[Item]?> in
guard !items.isEmpty else {
return .value(nil)
}
return Promise(value: items)
}
有时也会出现在第二个 then
中会返回 nil。这就将检查 nil 的工作交给了 promise 的调用者。
通常,更好的做法是将这些异常情况当成错误来处理,而不是把它当前正常情况来对待。对于这种情况,你可以创建一个特殊的错误类型来表示这种情况。
return firstly {
getItems()
}.map { items -> [Item]> in
guard !items.isEmpty else {
throw MyError.emptyItems
}
return items
}
注意:当别人的 API 返回一个可选项时,你可以通过使用
compactMap
将 nil 转化为 error。
技巧和窍门
后台加载成员变量
class MyViewController: UIViewController {
private let ambience: Promise<AVAudioPlayer> = DispatchQueue.global().async(.promise) {
guard let asset = NSDataAsset(name: "CreepyPad") else { throw PMKError.badInput }
let player = try AVAudioPlayer(data: asset.data)
player.prepareToPlay()
return player
}
}
<a name="WPj42"></a>
链接动画
firstly {
UIView.animate(.promise, duration: 0.3) {
self.button1.alpha = 0
}
}.then {
UIView.animate(.promise, duration: 0.3) {
self.button2.alpha = 1
}
}.then {
UIView.animate(.promise, duration: 0.3) {
adjustConstraints()
self.view.layoutIfNeeded()
}
}
将 Promise 的结果转化为 void
有时候抹除 promise 的执行结果后很方便。比如由于 UIKit 执行结束后会返回一个 bool 值,所以 UIView.animate(.promise)
返回的类型为 Guarantee<Bool>
。但是,我们通常不需要这个返回值,而且将这个返回值丢弃后(即将结果转化为 void),会让调用链变得更加简单。此时,我们可以使用 asVoid()
进行转化:
UIView.animate(.promise, duration: 0.3) {
self.button1.alpha = 0
}.asVoid().done(self.nextStep)
当我们用 when
组合多个 promise 时, asVoid()
就变得非常有用:
let p1 = foo()
let p2 = bar()
let p3 = baz()
//…
let p10 = fluff()
when(fulfilled: p1.asVoid(), p2.asVoid(), /*…*/, p10.asVoid()).then {
let value1 = p1.value! // safe bang since all the promises fulfilled
// …
let value10 = p10.value!
}.catch {
//…
}
你基本遇不到这么多参数的情况,所以 when
最多提供了 5 个参数
阻塞(等待)
有时在等待异步任务完成时,你需要阻塞主线程。这时,你可以(谨慎)使用 wait
:
public extension UNUserNotificationCenter {
var wasPushRequested: Bool {
let settings = Guarantee(resolver: getNotificationSettings).wait()
return settings != .notDetermined
}
}
Promise 的内部任务的回调不能在当前线程上执行,否则将会死锁。
在 Background 线程/队列上执行调用链
first
中故意没有设计 queue 参数。这样做的愿意可以查看 ticket tracker。
所以,当你希望在 Background 队列中执行一个调用链时,你必须使用 DispatchQueue.async
:
DispatchQueue.global().async(.promise) {
return value
}.done { value in
//…
}
由于 Swift 编译器类型推断上的问题,所以这个函数无法返回 Promise。因此,当你在后台队列开始运行 Promise 时,需要执行以下操作:
Promise { seal in
DispatchQueue.global().async {
seal(value)
}
}.done { value in
//…
}
或者更简单(尽管有警告,请查看 wait
文档):
DispatchQueue.global().async(.promise) {
return try fetch().wait()
}.done { value in
//…
}
但是,你应该不会经常这样写。当你想用这种技术的时候,你或许应该修改 fetch
中的代码,来使它运行在后台线程上。
Promise 抽象异步操作,所以支持并实现了这种模式。在设计 API 时,你不应该让使用者去操心你提供的方法到底运行在什么队列上。