then and done
一个典型的链条⛓️
firstly {
login()
}.then { creds in
fetch(avatar: creds.user)
}.done { image in
self.imageView = image
}
如果这段代码使用之前的方式可能会是这样
login { creds, error in
if let creds = creds {
fetch(avatar: creds.user) { image, error in
if let image = image {
self.imageView = image
}
}
}
}
其中重要的语法有then 是另一种方式的回调方法,但是承诺链比较容易理解,一个异步操作之后接着去执行下一个操作。
done是一样的,但是done不返回promise,它通常是链条成功部分的终结。
还有就是错误捕捉,比如我们以前的方法会有成功和错误的两个回调,但是现在使用了promise只需要返回一个promise就行了。如下所示
func login() -> Promise<Creds>
// Compared with:
func login(completion: (Creds?, Error?) -> Void)
// ^^ ugh. Optionals. Double optionals.
catch
那么错误是怎么捕捉到的呢,那么就需要用到promise的另一个重要的方法catch
firstly {
login()
}.then { creds in
fetch(avatar: creds.user)
}.done { image in
self.imageView = image
}.catch {
// any errors in the whole chain land here
}
每个promise
都代表一个异步任务的对象,如果任务失败,诺言会失败,执行reject
状态,如果诺言执行成功就是fulfilled
状态,如果执行失败会跳过后面的所有then操作进入到catch
当中。
ensure and finally
有时候我们需要执行一些必须要执行的操作,比如无论网络请求失败还是成功都隐藏请求动画,那么就需要用到ensure语句了。
无论你的promise执行结果如何,ensure当中的代码块都将被执行。
firstly {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
return login()
}.then {
fetch(avatar: $0.user)
}.done {
self.imageView = $0
}.ensure {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}.catch {
//…
}
另外你也可以使用finally
方法在链条的最底部执行,无须返回任何值。
spinner(visible: true)
firstly {
foo()
}.done {
//…
}.catch {
//…
}.finally {
self.spinner(visible: false)
}
when
使用完成处理程序,对多个异步操作的响应要么很慢,要么很难。慢意味着连续执行:
when
有点像GCD
当中的DispatchGroup
做完之后再通知到接下来的操作。
firstly {
when(fulfilled: operation1(), operation2())
}.done { result1, result2 in
//…
}
when
接受承诺,等待他们解决并返回包含结果的承诺。
与任何承诺链一样,如果任何组件承诺失败,链将调用下一个catch
。
Making Promises
那么如何使自己的项目支持promise?
标准扩展程序将带您走很长的路,但有时您仍然需要启动自己的链。也许您正在使用不提供承诺的第三方API,或者您可能编写了自己的异步系统。无论哪种方式,添加承诺都很容易。如果您查看标准扩展的代码,您会看到它使用下面描述的相同方法。
假设我们有以下方法:
func fetch(completion: (String?, Error?) -> Void){
}
我们可以使用下面方法变成promise
func fetch() -> Promise<String> {
return Promise{
seal in
fetch(completion: seal.resolve)
}
// 或者 return Promise { fetch(completion: $0.resolve) }
}
或者
func fetch() -> Promise<String> {
return Promise<String>{ seal in
fetch{ (result, error) in
seal.resolve(result, error)
}
}
}
Promise的初始化程序为您提供的seal对象定义了许多处理平常的完成处理程序的方法。它甚至涵盖了各种罕见的情况,因此您可以轻松地将promises添加到现有代码库中。
注意:在PMK 4中,此初始化程序为闭包提供了两个参数: fulfill和reject。PMK 5和6为您提供了具有两种方法fulfill和 reject方法的对象,但也提供了该方法的许多变体resolve。您通常只需将完成处理程序参数传递给,resolve并让Swift找出适用于您的特定情况的变体(如上例所示)。
还可以使用fulfill 和 reject方法,比如上面的代码可以改成下面的样子。
func fetch() -> Promise<String> {
return Promise<String>{ seal in
fetch{ (result, error) in
if let error = error {
seal.reject(error)
}else if let success = result{
seal.fulfill(success)
}else{
seal.reject(PMKError.invalidCallingConvention)
}
}
}
}
Guarantee
自从PromiseKit 5,提供了Guarantee作为补充 Promise。这样做是为了补充Swift强大的错误处理系统。
Guarantee永不失败,所以不能拒绝。一个很好的例子是after:
firstly {
after(seconds: 0.1)
}.done {
// there is no way to add a `catch` because after cannot fail.
}
APIs That Use Promises
class MyRestAPI {
func user() -> Promise<User> {
return firstly {
URLSession.shared.dataTask(.promise, with: url)
}.compactMap {
try JSONSerialization.jsonObject(with: $0.data) as? [String: Any]
}.map { dict in
User(dict: dict)
}
}
func avatar() -> Promise<UIImage> {
return user().then { user in
URLSession.shared.dataTask(.promise, with: user.imageUrl)
}.compactMap {
UIImage(data: $0.data)
}
}
}
通过这种方式,异步链可以干净地无缝地整合来自整个应用程序的代码,而不会违反架构边界。
Background Work
class MyRestAPI {
func avatar() -> Promise<UIImage> {
let bgq = DispatchQueue.global(qos: .userInitiated)
return firstly {
user()
}.then(on: bgq) { user in
URLSession.shared.dataTask(.promise, with: user.imageUrl)
}.compactMap(on: bgq) {
UIImage(data: $0)
}
}
}
所有PromiseKit处理程序都带有一个on参数,允许您指定运行处理程序的调度队列。默认值始终是主队列。
PromiseKit 完全是线程安全的。
Failing Chains
如果错误发生在链条中间,只需抛出一个错误。
firstly {
foo()
}.then { baz in
bar(baz)
}.then { result in
guard !result.isBad else { throw MyError.myIssue }
//…
return doOtherThing()
}
错误将在下一个catch处理程序中显示。
由于promises处理抛出的错误,除非你真的想在本地处理错误,否则你不必将调用函数包装到块中:
foo().then { baz in
bar(baz)
}.then { result in
try doOtherThing()
}.catch { error in
// if doOtherThing() throws, we end up here
}
抽象异步性 Abstracting Away Asynchronicity
var fetch = API.fetch()
override func viewDidAppear() {
fetch.then { items in
//…
}
}
func buttonPressed() {
fetch.then { items in
//…
}
}
func refresh() -> Promise {
// ensure only one fetch operation happens at a time
if fetch.isResolved {
startSpinner()
fetch = API.fetch().ensure {
stopSpinner()
}
}
return fetch
}
使用promises,您无需担心异步操作何时完成。就像现在一样。
在上面,我们看到您可以根据自己的喜好多次调用then。所有块将按添加顺序执行。
Minimum Duration
有时您需要一项任务至少花费一定的时间。(例如,您希望显示进度条,但如果它显示的时间少于0.3秒,则UI对用户显示为断开。)
let waitAtLeast = after(seconds: 0.3)
firstly {
foo()
}.then {
waitAtLeast
}.done {
//…
}
上面的代码有效,因为我们在工作之前创建了延迟foo()。当我们等待这个承诺时,要么它已经超时,要么我们将等待0.3秒的任何剩余时间,然后继续链。
Cancellation
Promise没有cancel函数,但它们通过符合CancellableError协议的特殊错误类型支持取消。
func foo() -> (Promise<Void>, cancel: () -> Void) {
let task = Task(…)
var cancelme = false
let promise = Promise<Void> { seal in
task.completion = { value in
guard !cancelme else { return reject(PMKError.cancelled) }
seal.fulfill(value)
}
task.start()
}
let cancel = {
cancelme = true
task.cancel()
}
return (promise, cancel)
}
被取消的链条默认不会进入到catch方法,然后也可以接收取消中断,如下
foo.then {
//…
}.catch(policy: .allErrors) {
// cancelled errors are handled *as well*
}
Saving Previous Results
举个例子
login().then { username in
fetch(avatar: username)
}.done { image in
//…
}
如果想在done在同时访问username和image呢
可以使用下面的方法
login().then { username in
fetch(avatar: username).done { image in
// we have access to both `image` and `username`
}
}.done {
// the chain still continues as you'd expect
}
然而,这种嵌套降低了链条的清晰度。相反,我们可以使用Swift元组:
//简化版
login().then { username in
fetch(avatar: username).map { ($0, username) }
}.then { image, username in
//…
}
Chaining Sequences
当你在一个数组中有一系列的任务要反应的时候
// fade all visible table cells one by one in a “cascading” effect
var fade = Guarantee.value(Bool())
for cell in tableView.visibleCells {
fade = fade.then {_ in
UIView.animate(.promise, duration: 1, animations: {
cell.alpha = 0
})
}
}
fade.done {_ in
}
或者你有一个包含promise的数组
var foo = Promise()
for nextPromise in arrayOfPromises {
foo = foo.then { nextPromise }
}
foo.done {
// finish
}
之前我们知道可以使用when同时异步执行多个promise,这种方式完成的速度更快,但是有时候我们需要promise顺序一个一个的执行,比如上面的动画。
Promise采用关联对象进行保存对象
oc中运行时关联对象 宏定义写法
extern void *PMKManualReferenceAssociatedObject;
#define PMKRetain(obj) objc_setAssociatedObject(obj, PMKManualReferenceAssociatedObject, obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
#define PMKRelease(obj) objc_setAssociatedObject(obj, PMKManualReferenceAssociatedObject, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC)