536 lines
16 KiB
Markdown
536 lines
16 KiB
Markdown
# `then` and `done`
|
||
|
||
Here is a typical promise chain:
|
||
|
||
```swift
|
||
firstly {
|
||
login()
|
||
}.then { creds in
|
||
fetch(avatar: creds.user)
|
||
}.done { image in
|
||
self.imageView = image
|
||
}
|
||
```
|
||
|
||
If this code used completion handlers, it would look like this:
|
||
|
||
```swift
|
||
login { creds, error in
|
||
if let creds = creds {
|
||
fetch(avatar: creds.user) { image, error in
|
||
if let image = image {
|
||
self.imageView = image
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
`then` *is* just another way to structure completion handlers, but it is also quite a
|
||
bit more. At this initial stage of our understanding, it mostly helps
|
||
readability. The promise chain above is easy to scan and understand: one asynchronous
|
||
operation leads into the other, line by line. It's as close to
|
||
procedural code as we can easily come given the current state of Swift.
|
||
|
||
`done` is the same as `then` but you cannot return a promise. It is
|
||
typically the end of the “success” part of the chain. Above, you can see that we
|
||
receive the final image in our `done` and use it to set up the UI.
|
||
|
||
Let’s compare the signatures of the two login methods:
|
||
|
||
```swift
|
||
func login() -> Promise<Creds>
|
||
|
||
// Compared with:
|
||
|
||
func login(completion: (Creds?, Error?) -> Void)
|
||
// ^^ ugh. Optionals. Double optionals.
|
||
```
|
||
|
||
The distinction is that with promises, your functions return *promises* instead
|
||
of accepting and running callbacks. Each handler in a chain returns a promise.
|
||
`Promise` objects define the `then` method, which waits for the completion of the
|
||
promise before continuing the chain. Chains resolve procedurally, one promise
|
||
at a time.
|
||
|
||
A `Promise` represents the future value of an asynchronous task. It has a type
|
||
that represents the type of object it wraps. For example, in the example above,
|
||
`login` is a function that returns a `Promise` that *will* represent an instance
|
||
of `Creds`.
|
||
|
||
> *Note*: `done` is new to PromiseKit 5. We previously defined a variant of `then` 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 of `done` lets you type out promise chains that
|
||
compile without additional qualification to help the compiler figure out type information.
|
||
|
||
---
|
||
|
||
You may notice that unlike the completion pattern, the promise chain appears to
|
||
ignore errors. This is not the case! In fact, it has the opposite effect: the promise
|
||
chain makes error handling more accessible and makes errors harder to ignore.
|
||
|
||
|
||
# `catch`
|
||
|
||
With promises, errors cascade along the promise chain, ensuring that your apps are
|
||
robust and your code is clear:
|
||
|
||
```swift
|
||
firstly {
|
||
login()
|
||
}.then { creds in
|
||
fetch(avatar: creds.user)
|
||
}.done { image in
|
||
self.imageView = image
|
||
}.catch {
|
||
// any errors in the whole chain land here
|
||
}
|
||
```
|
||
|
||
> Swift emits a warning if you forget to `catch` a chain. But we'll
|
||
> talk about that in more detail later.
|
||
|
||
Each promise is an object that represents an individual, asychnronous task.
|
||
If a task fails, its promise becomes *rejected*. Chains that contain rejected
|
||
promises skip all subsequent `then`s. Instead, the next `catch` is executed.
|
||
(Strictly speaking, *all* subsequent `catch` handlers are executed.)
|
||
|
||
For fun, let’s compare this pattern with its completion handler equivalent:
|
||
|
||
```swift
|
||
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
|
||
}
|
||
}
|
||
```
|
||
|
||
The use of `guard` and a consolidated error handler help, but the promise chain’s
|
||
readability speaks for itself.
|
||
|
||
|
||
# `ensure`
|
||
|
||
We have learned to compose asynchronicity. Next let’s extend our primitives:
|
||
|
||
```swift
|
||
firstly {
|
||
UIApplication.shared.isNetworkActivityIndicatorVisible = true
|
||
return login()
|
||
}.then {
|
||
fetch(avatar: $0.user)
|
||
}.done {
|
||
self.imageView = $0
|
||
}.ensure {
|
||
UIApplication.shared.isNetworkActivityIndicatorVisible = false
|
||
}.catch {
|
||
//…
|
||
}
|
||
```
|
||
|
||
No matter the outcome of your chain—-failure or success—-your `ensure`
|
||
handler is always called.
|
||
|
||
Let’s compare this pattern with its completion handler equivalent:
|
||
|
||
```swift
|
||
UIApplication.shared.isNetworkActivityIndicatorVisible = true
|
||
|
||
func handle(error: Error) {
|
||
UIApplication.shared.isNetworkActivityIndicatorVisible = false
|
||
//…
|
||
}
|
||
|
||
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
|
||
UIApplication.shared.isNetworkActivityIndicatorVisible = false
|
||
}
|
||
}
|
||
```
|
||
|
||
It would be very easy for someone to amend this code and forget to unset
|
||
the activity indicator, leading to a bug. With promises, this type of error is
|
||
almost impossible: the Swift compiler resists your supplementing the chain without
|
||
using promises. You almost won’t need to review the pull requests.
|
||
|
||
> *Note*: PromiseKit has perhaps capriciously switched between the names `always`
|
||
and `ensure` 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:
|
||
|
||
```
|
||
spinner(visible: true)
|
||
|
||
firstly {
|
||
foo()
|
||
}.done {
|
||
//…
|
||
}.catch {
|
||
//…
|
||
}.finally {
|
||
self.spinner(visible: false)
|
||
}
|
||
```
|
||
|
||
|
||
# `when`
|
||
|
||
With completion handlers, reacting to multiple asynchronous operations is either
|
||
slow or hard. Slow means doing it serially:
|
||
|
||
```swift
|
||
operation1 { result1 in
|
||
operation2 { result2 in
|
||
finish(result1, result2)
|
||
}
|
||
}
|
||
```
|
||
|
||
The fast (*parallel*) path code makes the code less clear:
|
||
|
||
```swift
|
||
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 are easier:
|
||
|
||
```swift
|
||
firstly {
|
||
when(fulfilled: operation1(), operation2())
|
||
}.done { result1, result2 in
|
||
//…
|
||
}
|
||
```
|
||
|
||
`when` takes promises, waits for them to resolve and returns a promise containing the results.
|
||
|
||
As with any promise chain, if any of the component promises fail, the chain calls the next `catch`.
|
||
|
||
|
||
# PromiseKit Extensions
|
||
|
||
When we made PromiseKit, we understood that we wanted to use *only* promises to implement
|
||
asynchronous behavior. So wherever possible, we offer extensions to Apple’s APIs that reframe
|
||
the API in terms of promises. For example:
|
||
|
||
```swift
|
||
firstly {
|
||
CLLocationManager.promise()
|
||
}.then { location in
|
||
CLGeocoder.reverseGeocode(location)
|
||
}.done { placemarks in
|
||
self.placemark.text = "\(placemarks.first)"
|
||
}
|
||
```
|
||
|
||
To use these extensions, you need to specify subspecs:
|
||
|
||
```ruby
|
||
pod "PromiseKit"
|
||
pod "PromiseKit/CoreLocation"
|
||
pod "PromiseKit/MapKit"
|
||
```
|
||
|
||
All of these extensions are available at the [PromiseKit organization](https://github.com/PromiseKit).
|
||
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](https://github.com/PromiseKit/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.
|
||
|
||
Let’s say we have the following method:
|
||
|
||
```swift
|
||
func fetch(completion: (String?, Error?) -> Void)
|
||
```
|
||
|
||
How do we convert this to a promise? Well, it's easy:
|
||
|
||
```swift
|
||
func fetch() -> Promise<String> {
|
||
return Promise { fetch(completion: $0.resolve) }
|
||
}
|
||
```
|
||
|
||
You may find the expanded version more readable:
|
||
|
||
```swift
|
||
func fetch() -> Promise<String> {
|
||
return Promise { seal in
|
||
fetch { result, error in
|
||
seal.resolve(result, error)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
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` and `reject`. PMK 5 and 6 give you an object that has both `fulfill` and
|
||
`reject` methods, but also many variants of the method `resolve`. You can
|
||
typically just pass completion handler parameters to `resolve` 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
|
||
a `Resolver` object. Thus do `seal(value)` rather than `seal.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 to
|
||
`Promise`. We do this to complement Swift’s strong error handling system.
|
||
|
||
Guarantees *never* fail, so they cannot be rejected. A good example is `after`:
|
||
|
||
```
|
||
firstly {
|
||
after(seconds: 0.1)
|
||
}.done {
|
||
// there is no way to add a `catch` because after cannot fail.
|
||
}
|
||
```
|
||
|
||
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;
|
||
|
||
```swift
|
||
func fetch() -> Promise<String> {
|
||
return Guarantee { seal in
|
||
fetch { result in
|
||
seal(result)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Which could be reduced to:
|
||
|
||
```swift
|
||
func fetch() -> Promise<String> {
|
||
return Guarantee(resolver: fetch)
|
||
}
|
||
```
|
||
|
||
# `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 with
|
||
`PMKError.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 of `then` into `then`, `map` and
|
||
`done`. After using these new functions, we realized this is much nicer in practice,
|
||
so we added `compactMap` as well (modeled on `Optional.compactMap`).
|
||
|
||
`compactMap` facilitates quick composition of promise chains. For example:
|
||
|
||
```swift
|
||
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
|
||
}
|
||
```
|
||
|
||
> *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`.
|
||
|
||
```swift
|
||
firstly {
|
||
foo()
|
||
}.get { foo in
|
||
//…
|
||
}.done { foo in
|
||
// same foo!
|
||
}
|
||
```
|
||
|
||
|
||
# `tap`
|
||
|
||
We provide `tap` for debugging. It's the same as `get` but provides the
|
||
`Result<T>` of the `Promise` so you can inspect the value of the chain at this
|
||
point without causing any side effects:
|
||
|
||
```swift
|
||
firstly {
|
||
foo()
|
||
}.tap {
|
||
print($0)
|
||
}.done {
|
||
//…
|
||
}.catch {
|
||
//…
|
||
}
|
||
```
|
||
|
||
|
||
# Supplement
|
||
|
||
## `firstly`
|
||
|
||
We've used `firstly` several times on this page, but what is it, really? In fact,
|
||
it is just [syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar).
|
||
You don’t really need it, but it helps to make your chains more readable. Instead of:
|
||
|
||
```swift
|
||
firstly {
|
||
login()
|
||
}.then { creds in
|
||
//…
|
||
}
|
||
```
|
||
|
||
You could just do:
|
||
|
||
```swift
|
||
login().then { creds in
|
||
//…
|
||
}
|
||
```
|
||
|
||
Here is a key understanding: `login()` returns a `Promise`, and all `Promise`s have a `then` function. `firstly` returns a `Promise`, and `then` returns a `Promise`, too! But don’t worry too much about these details. Learn the *patterns* to start with. Then, when you are ready to advance, learn the underlying architecture.
|
||
|
||
|
||
## `when` Variants
|
||
|
||
`when` is one of PromiseKit’s more useful functions, and so we offer several variants.
|
||
|
||
* The default `when`, and the one you should typically use, is `when(fulfilled:)`. This variant
|
||
waits on all its component promises, but if any fail, `when` fails too, and thus the 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:)` waits even if one or more of its component promises fails. The value produced
|
||
by this variant of `when` is an array of `Result<T>`. Consequently, this variant requires all its
|
||
component promises to have the same generic type. See our advanced patterns guide for work-arounds
|
||
for this limitation.
|
||
|
||
* The `race` variant lets you *race* several promises. Whichever finishes first is the result. See the
|
||
advanced patterns guide for typical usage.
|
||
|
||
|
||
## Swift Closure Inference
|
||
|
||
Swift automatically infers returns and return types for one-line closures.
|
||
The following two forms are the same:
|
||
|
||
```swift
|
||
foo.then {
|
||
bar($0)
|
||
}
|
||
|
||
// is the same as:
|
||
|
||
foo.then { baz -> Promise<String> in
|
||
return bar(baz)
|
||
}
|
||
```
|
||
|
||
Our documentation often omits the `return` for clarity.
|
||
|
||
However, this shorthand is both a blessing and a curse. You may find that the Swift compiler
|
||
often fails to infer return types properly. See our [Troubleshooting Guide](Troubleshooting.md) if
|
||
you require further assistance.
|
||
|
||
> By adding `done` to PromiseKit 5, we have managed to avoid many of these common
|
||
pain points in using PromiseKit and Swift.
|
||
|
||
|
||
|
||
# Further Reading
|
||
|
||
The above information is the 90% you will use. We **strongly** suggest reading the
|
||
[API Reference].
|
||
There are numerous little
|
||
functions that may be useful to you, and the documentation for everything outlined above
|
||
is more thorough at the source.
|
||
|
||
In Xcode, don’t forget to option-click on PromiseKit functions to access this
|
||
documentation while you're coding.
|
||
|
||
Here are some recent articles that document PromiseKit 5+:
|
||
|
||
* [Using Promises - Agostini.tech](https://agostini.tech/2018/10/08/using-promisekit)
|
||
|
||
Careful with general online references, many of them refer to PMK < 5 which has a subtly
|
||
different API (sorry about that, but Swift has changed a lot over the years and thus
|
||
we had to too).
|
||
|
||
|
||
[API Reference]: https://promisekit.org/reference/
|