then
and done
典型使用:
1 | firstly { |
如果这段代码使用了completion handlers
,它将如下所示:
1 | login { creds, error in |
then
只是构造completion handlers
的另一种方式, 但也远不止如此。在我们理解的最初阶段,它主要有助于可读性。上面的promise链
很容易浏览和理解:一个异步操作一行一行地通向另一个。鉴于Swift的当前状态,它尽可能接近程序代码。
done
和 then
,但是你不能返回一个promise
. 这通常是链条中success
部分的终点. 上面, 您可以看到我们在 done
时收到了最终的图像,并使用它来设置用户界面。
两种实现方法的比较:
1 | func login() -> Promise<Creds> |
区别在于,对于Promise
,您的函数返回Promise
,而不是接受和运行回调。链中的每个handler
都会返回一个Promise
。Promise
对象定义了then
方法,该方法在链的执行之前等待Promise
的完成。锁链按程序解决,一次一个Promise
。
Promise
代表异步任务的返回值。它有一个表示它包装的对象类型的类型。例如,在上面的例子中,login
是一个返回Promise
的函数,该Promise
将代表Creds实例。
Note:
done
is new to PromiseKit 5. We previously defined a variant ofthen
that
did not require you to return a promise. Unfortunately, this convention often confused
Swift and led to odd and hard-to-debug error messages. It also made using PromiseKit
more painful. The introduction ofdone
lets you type out promise chains that
compile without additional qualification to help the compiler figure out type information.
您可能会注意到,与completion模式不同,promise链似乎
忽略errors。事实并非如此!事实上,它有相反的效果:promise
链使得errors处理更容易访问,也使得errors更难忽略。
catch
有了promise,errors就会沿着promise链级联,确保你的应用程序
健壮,代码清晰:
1 | firstly { |
Swift emits a warning if you forget to
catch
a chain. But we’ll
talk about that in more detail later.
每个promise都是一个对象,代表一个单独的异步任务。
如果任务失败, promise就会变成rejected。 Chains that contain rejected
包含rejected的promise链跳过所有后续的promise。相反,执行下一个捕获。
(严格来说,所有后续的捕获处理程序都会被执行。)
completion handler
与promise
的比较
1 | func handle(error: Error) { |
guard
和error handler
的使用有所帮助,但是promise链
可读性不言自明。
ensure
我们已经学会了合成异步性。接下来,让我们扩展:
1 | firstly { |
不管你的链条的结果是什么——失败还是成功 ensure
handler 总是被调用.
ensure
模式与其等价的完成处理程序进行比较:
1 | UIApplication.shared.isNetworkActivityIndicatorVisible = true |
对某人来说,修改这个代码并忘记取消设置是非常容易的
导致错误的活动指示器。有了promises,这种类型的错误是
几乎不可能:Swift编译器拒绝你在没有
使用promises。您几乎不需要审查拉动式请求。
Note: PromiseKit has perhaps capriciously switched between the names
always
andensure
for this function several times in the past. Sorry about this. We suck.
You can also use finally
as an ensure
that terminates the promise chain and does not return a value:
您也可以使用finally
作为结束的promise链且不返回值的ensure
:
1 | spinner(visible: true) |
when
对多个异步操作的completion handlers反应是缓慢的,意味着连续执行:
1 | operation1 { result1 in |
快速(parallel)路径代码使代码不太清晰
1 | var result1: …! |
如果使用Promises就更清晰了:
1 | firstly { |
when
promise代表:等待它们解决并返回包含结果的promise。
与任何promise链一样,如果任何组件promise失败,该链将调用下一个catch
。
PromiseKit Extensions
当我们使用PromiseKit, 我们是想想通过promise来实现
异步行为。因此,只要有可能,我们会对苹果的APIs进行扩展,重新构建
promise方面的应用编程接口。例如:
1 | firstly { |
要使用这些扩展,您需要指定子代码:
1 | pod "PromiseKit" |
All of these extensions are available at the PromiseKit organization.
Go there to see what’s available and to read the source code and documentation. Every file and function
has been copiously documented.
We also provide extensions for common libraries such as Alamofire.
Making Promises
The standard extensions will take you a long way, but sometimes you’ll still need to start chains
of your own. Maybe you’re using a third party API that doesn’t provide promises, or perhaps you wrote
your own asynchronous system. Either way, it’s easy to add promises. If you look at the code of the
standard extensions, you’ll see that it uses the same approach described below.
标准的扩展会让你走很长一段路,但是有时候你仍然需要启动链
你自己的。也许你使用的是不提供promises的第三方应用编程接口,或者你写道
你自己的异步系统。不管怎样,添加promises都很容易。如果你看看
标准扩展,您会看到它使用了下面描述的相同方法。
Let’s say we have the following method:
1 | func fetch(completion: (String?, Error?) -> Void) |
How do we convert this to a promise? Well, it’s easy:
1 | func fetch() -> Promise<String> { |
You may find the expanded version more readable:
1 | func fetch() -> Promise<String> { |
The seal
object that the Promise
initializer provides to you defines
many methods for handling garden-variety completion handlers. It even
covers a variety of rarer situations, thus making it easy for you to add
promises to an existing codebase.
Note: We tried to make it so that you could just do
Promise(fetch)
, but we
were not able to make this simpler pattern work universally without requiring
extra disambiguation for the Swift compiler. Sorry; we tried.
Note: In PMK 4, this initializer provided two parameters to your closure:
fulfill
andreject
. PMK 5 and 6 give you an object that has bothfulfill
andreject
methods, but also many variants of the methodresolve
. You can
typically just pass completion handler parameters toresolve
and let Swift figure
out which variant to apply to your particular case (as shown in the example above).
Note
Guarantees
(below) have a slightly different initializer (since they
cannot error) so the parameter to the initializer closure is just a closure. Not
aResolver
object. Thus doseal(value)
rather thanseal.fulfill(value)
. This
is because there is no variations in what guarantees can be sealed with, they can
only fulfill.
Guarantee<T>
Since PromiseKit 5, we have provided Guarantee
as a supplementary class toPromise
. We do this to complement Swift’s strong error handling system.
Guarantees never fail, so they cannot be rejected. A good example is after
:
1 | firstly { |
Swift warns you if you don’t terminate a regular Promise
chain (i.e., not
a Guarantee
chain). You’re expected to silence this warning by supplying
either a catch
or a return
. (In the latter case, you will then have to catch
at the point where you receive that promise.)
Use Guarantee
s wherever possible so that your code has error handling where
it’s required and no error handling where it’s not required.
In general, you should be able to use Guarantee
s and Promise
s interchangeably,
We have gone to great lengths to try and ensure this, so please open a ticket
if you find an issue.
If you are creating your own guarantees the syntax is simpler than that of promises;
1 | func fetch() -> Promise<String> { |
Which could be reduced to:
1 | func fetch() -> Promise<String> { |
map
, compactMap
, etc.
then
provides you with the result of the previous promise and requires you to return
another promise.
map
provides you with the result of the previous promise and requires you to return
an object or value type.
compactMap
provides you with the result of the previous promise and requires you
to return an Optional
. If you return nil
, the chain fails withPMKError.compactMap
.
Rationale: Before PromiseKit 4,
then
handled all these cases, and it was
painful. We hoped the pain would disappear with new Swift versions. However,
it has become clear that the various pain points are here to stay. In fact, we
as library authors are expected to disambiguate at the naming level of our API.
Therefore, we have split the three main kinds ofthen
intothen
,map
anddone
. After using these new functions, we realized this is much nicer in practice,
so we addedcompactMap
as well (modeled onOptional.compactMap
).
compactMap
facilitates quick composition of promise chains. For example:
1 | firstly { |
Tip: We also provide most of the functional methods you would expect for sequences,
e.g.,map
,thenMap
,compactMapValues
,firstValue
, etc.
get
We provide get
as a done
that returns the value fed to get
.
1 | firstly { |
tap
We provide tap
for debugging. It’s the same as get
but provides theResult<T>
of the Promise
so you can inspect the value of the chain at this
point without causing any side effects:
1 | firstly { |
Supplement
firstly
We’ve used firstly
several times on this page, but what is it, really? In fact,
it is just syntactic sugar.
You don’t really need it, but it