Add PromiseKit dependency
- Added PromiseKit dependency
This commit is contained in:
211
Carthage/Checkouts/PromiseKit/Documentation/Appendix.md
vendored
Normal file
211
Carthage/Checkouts/PromiseKit/Documentation/Appendix.md
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
# Common Misusage
|
||||
|
||||
## Doubling up Promises
|
||||
|
||||
Don’t do this:
|
||||
|
||||
```swift
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Do this:
|
||||
|
||||
```swift
|
||||
func toggleNetworkSpinnerWithPromise<T>(funcToCall: () -> Promise<T>) -> Promise<T> {
|
||||
return firstly {
|
||||
setNetworkActivityIndicatorVisible(true)
|
||||
return funcToCall()
|
||||
}.always {
|
||||
setNetworkActivityIndicatorVisible(false)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You already *had* a promise, you don’t need to wrap it in another promise.
|
||||
|
||||
|
||||
## Optionals in Promises
|
||||
|
||||
When we see `Promise<Item?>`, it usually implies a misuse of promises. For
|
||||
example:
|
||||
|
||||
```swift
|
||||
return firstly {
|
||||
getItems()
|
||||
}.then { items -> Promise<[Item]?> in
|
||||
guard !items.isEmpty else {
|
||||
return .value(nil)
|
||||
}
|
||||
return Promise(value: items)
|
||||
}
|
||||
```
|
||||
|
||||
The second `then` chooses to return `nil` in some circumstances. This choice
|
||||
imposes the need to check for `nil` on the consumer of the promise.
|
||||
|
||||
It's usually better to shunt these sorts of exceptions away from the
|
||||
happy path and onto the error path. In this case, we can create a specific
|
||||
error type for this condition:
|
||||
|
||||
```swift
|
||||
return firstly {
|
||||
getItems()
|
||||
}.map { items -> [Item]> in
|
||||
guard !items.isEmpty else {
|
||||
throw MyError.emptyItems
|
||||
}
|
||||
return items
|
||||
}
|
||||
```
|
||||
|
||||
> *Note*: Use `compactMap` when an API outside your control returns an Optional and you want to generate an error instead of propagating `nil`.
|
||||
|
||||
# Tips n’ Tricks
|
||||
|
||||
## Background-Loaded Member Variables
|
||||
|
||||
```swift
|
||||
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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Chaining Animations
|
||||
|
||||
```swift
|
||||
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()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Voiding Promises
|
||||
|
||||
It is often convenient to erase the type of a promise to facilitate chaining.
|
||||
For example, `UIView.animate(.promise)` returns `Guarantee<Bool>` because UIKit’s
|
||||
completion API supplies a `Bool`. However, we usually don’t need this value and
|
||||
can chain more simply if it is discarded (that is, converted to `Void`). We can use
|
||||
`asVoid()` to achieve this conversion:
|
||||
|
||||
```swift
|
||||
UIView.animate(.promise, duration: 0.3) {
|
||||
self.button1.alpha = 0
|
||||
}.asVoid().done(self.nextStep)
|
||||
```
|
||||
|
||||
For situations in which we are combining many promises into a `when`, `asVoid()`
|
||||
becomes essential:
|
||||
|
||||
```swift
|
||||
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 {
|
||||
//…
|
||||
}
|
||||
```
|
||||
|
||||
You normally don't have to do this explicitly because `when` does it for you
|
||||
for up to 5 parameters.
|
||||
|
||||
|
||||
## Blocking (Await)
|
||||
|
||||
Sometimes you have to block the main thread to await completion of an asynchronous task.
|
||||
In these cases, you can (with caution) use `wait`:
|
||||
|
||||
```swift
|
||||
public extension UNUserNotificationCenter {
|
||||
var wasPushRequested: Bool {
|
||||
let settings = Guarantee(resolver: getNotificationSettings).wait()
|
||||
return settings != .notDetermined
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The task under the promise **must not** call back onto the current thread or it
|
||||
will deadlock.
|
||||
|
||||
## Starting a Chain on a Background Queue/Thread
|
||||
|
||||
`firstly` deliberately does not take a queue. A detailed rationale for this choice
|
||||
can be found in the ticket tracker.
|
||||
|
||||
So, if you want to start a chain by dispatching to the background, you have to use
|
||||
`DispatchQueue.async`:
|
||||
|
||||
```swift
|
||||
DispatchQueue.global().async(.promise) {
|
||||
return value
|
||||
}.done { value in
|
||||
//…
|
||||
}
|
||||
```
|
||||
|
||||
However, this function cannot return a promise because of Swift compiler ambiguity
|
||||
issues. Thus, if you must start a promise on a background queue, you need to
|
||||
do something like this:
|
||||
|
||||
|
||||
```swift
|
||||
Promise { seal in
|
||||
DispatchQueue.global().async {
|
||||
seal(value)
|
||||
}
|
||||
}.done { value in
|
||||
//…
|
||||
}
|
||||
```
|
||||
|
||||
Or more simply (though with caveats; see the documentation for `wait`):
|
||||
|
||||
```swift
|
||||
DispatchQueue.global().async(.promise) {
|
||||
return try fetch().wait()
|
||||
}.done { value in
|
||||
//…
|
||||
}
|
||||
```
|
||||
|
||||
However, you shouldn't need to do this often. If you find yourself wanting to use
|
||||
this technique, perhaps you should instead modify the code for `fetch` to make it do
|
||||
its work on a background thread.
|
||||
|
||||
Promises abstract asynchronicity, so exploit and support that model. Design your
|
||||
APIs so that consumers don’t have to care what queue your functions run on.
|
480
Carthage/Checkouts/PromiseKit/Documentation/CommonPatterns.md
vendored
Normal file
480
Carthage/Checkouts/PromiseKit/Documentation/CommonPatterns.md
vendored
Normal file
@@ -0,0 +1,480 @@
|
||||
# Common Patterns
|
||||
|
||||
One feature of promises that makes them particularly useful is that they are composable.
|
||||
This fact enables complex, yet safe asynchronous patterns that would otherwise be quite
|
||||
intimidating when implemented with traditional methods.
|
||||
|
||||
|
||||
## Chaining
|
||||
|
||||
The most common pattern is chaining:
|
||||
|
||||
```swift
|
||||
firstly {
|
||||
fetch()
|
||||
}.then {
|
||||
map($0)
|
||||
}.then {
|
||||
set($0)
|
||||
return animate()
|
||||
}.ensure {
|
||||
cleanup()
|
||||
}.catch {
|
||||
handle(error: $0)
|
||||
}
|
||||
```
|
||||
|
||||
If you return a promise in a `then`, the next `then` *waits* on that promise
|
||||
before continuing. This is the essence of promises.
|
||||
|
||||
Promises are easy to compose, so they encourage you to develop highly asynchronous
|
||||
apps without fear of the spaghetti code (and associated refactoring pains) of
|
||||
asynchronous systems that use completion handlers.
|
||||
|
||||
|
||||
## APIs That Use Promises
|
||||
|
||||
Promises are composable, so return them instead of accepting completion blocks:
|
||||
|
||||
```swift
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This way, asynchronous chains can cleanly and seamlessly incorporate code from all over
|
||||
your app without violating architectural boundaries.
|
||||
|
||||
> *Note*: We provide [promises for Alamofire](https://github.com/PromiseKit/Alamofire-) too!
|
||||
|
||||
|
||||
## Background Work
|
||||
|
||||
```swift
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
All PromiseKit handlers take an `on` parameter that lets you designate the dispatch queue
|
||||
on which to run the handler. The default is always the main queue.
|
||||
|
||||
PromiseKit is *entirely* thread safe.
|
||||
|
||||
> *Tip*: With caution, you can have all `then`, `map`, `compactMap`, etc., run on
|
||||
a background queue. See `PromiseKit.conf`. Note that we suggest only changing
|
||||
the queue for the `map` suite of functions, so `done` and `catch` will
|
||||
continue to run on the main queue, which is *usually* what you want.
|
||||
|
||||
## Failing Chains
|
||||
|
||||
If an error occurs mid-chain, simply throw an error:
|
||||
|
||||
```swift
|
||||
firstly {
|
||||
foo()
|
||||
}.then { baz in
|
||||
bar(baz)
|
||||
}.then { result in
|
||||
guard !result.isBad else { throw MyError.myIssue }
|
||||
//…
|
||||
return doOtherThing()
|
||||
}
|
||||
```
|
||||
|
||||
The error will surface at the next `catch` handler.
|
||||
|
||||
Since promises handle thrown errors, you don't have to wrap calls to throwing functions
|
||||
in a `do` block unless you really want to handle the errors locally:
|
||||
|
||||
```swift
|
||||
foo().then { baz in
|
||||
bar(baz)
|
||||
}.then { result in
|
||||
try doOtherThing()
|
||||
}.catch { error in
|
||||
// if doOtherThing() throws, we end up here
|
||||
}
|
||||
```
|
||||
|
||||
> *Tip*: Swift lets you define an inline `enum Error` inside the function you
|
||||
are working on. This isn’t *great* coding practice, but it's better than
|
||||
avoiding throwing an error because you couldn't be bothered to define a good global
|
||||
`Error` `enum`.
|
||||
|
||||
|
||||
## Abstracting Away Asychronicity
|
||||
|
||||
```swift
|
||||
var fetch = API.fetch()
|
||||
|
||||
override func viewDidAppear() {
|
||||
fetch.then { items in
|
||||
//…
|
||||
}
|
||||
}
|
||||
|
||||
func buttonPressed() {
|
||||
fetch.then { items in
|
||||
//…
|
||||
}
|
||||
}
|
||||
|
||||
func refresh() {
|
||||
// ensure only one fetch operation happens at a time
|
||||
|
||||
if fetch.isResolved {
|
||||
startSpinner()
|
||||
fetch = API.fetch().ensure {
|
||||
stopSpinner()
|
||||
}
|
||||
}
|
||||
return fetch
|
||||
}
|
||||
```
|
||||
|
||||
With promises, you don’t need to worry about *when* your asynchronous operation
|
||||
finishes. Just act like it already has.
|
||||
|
||||
Above, we see that you can call `then` as many times on a promise as you
|
||||
like. All the blocks will be executed in the order they were added.
|
||||
|
||||
|
||||
## Chaining Sequences
|
||||
|
||||
When you have a series of tasks to perform on an array of data:
|
||||
|
||||
```swift
|
||||
// fade all visible table cells one by one in a “cascading” effect
|
||||
|
||||
let fade = Guarantee()
|
||||
for cell in tableView.visibleCells {
|
||||
fade = fade.then {
|
||||
UIView.animate(.promise, duration: 0.1) {
|
||||
cell.alpha = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
fade.done {
|
||||
// finish
|
||||
}
|
||||
```
|
||||
|
||||
> *Note*: You *usually* you want `when()`, since `when` executes all of its
|
||||
component promises in parallel and so completes much faster. Use the pattern
|
||||
shown above in situations where tasks *must* be run sequentially; animation
|
||||
is a good example.
|
||||
|
||||
We also provide `when(concurrently:)`, which lets you schedule more than
|
||||
one promise at a time if you need to.
|
||||
|
||||
## Timeout
|
||||
|
||||
```swift
|
||||
let fetches: [Promise<T>] = makeFetches()
|
||||
let timeout = after(seconds: 4)
|
||||
|
||||
race(when(fulfilled: fetches).asVoid(), timeout).then {
|
||||
//…
|
||||
}
|
||||
```
|
||||
|
||||
`race` continues as soon as one of the promises it is watching finishes.
|
||||
|
||||
Make sure the promises you pass to `race` are all of the same type. The easiest way
|
||||
to ensure this is to use `asVoid()`.
|
||||
|
||||
Note that if any component promise rejects, the `race` will reject, too.
|
||||
|
||||
|
||||
# Minimum Duration
|
||||
|
||||
Sometimes you need a task to take *at least* a certain amount of time. (For example,
|
||||
you want to show a progress spinner, but if it shows for less than 0.3 seconds, the UI
|
||||
appears broken to the user.)
|
||||
|
||||
```swift
|
||||
let waitAtLeast = after(seconds: 0.3)
|
||||
|
||||
firstly {
|
||||
foo()
|
||||
}.then {
|
||||
waitAtLeast
|
||||
}.done {
|
||||
//…
|
||||
}
|
||||
```
|
||||
|
||||
The code above works because we create the delay *before* we do work in `foo()`. By the
|
||||
time we get to waiting on that promise, either it will have already timed out or we will wait
|
||||
for whatever remains of the 0.3 seconds before continuing the chain.
|
||||
|
||||
|
||||
## Cancellation
|
||||
|
||||
Promises don’t have a `cancel` function, but they do support cancellation through a
|
||||
special error type that conforms to the `CancellableError` protocol.
|
||||
|
||||
```swift
|
||||
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)
|
||||
}
|
||||
```
|
||||
|
||||
Promises don’t have a `cancel` function because you don’t want code outside of
|
||||
your control to be able to cancel your operations--*unless*, of course, you explicitly
|
||||
want to enable that behavior. In cases where you do want cancellation, the exact way
|
||||
that it should work will vary depending on how the underlying task supports cancellation.
|
||||
PromiseKit provides cancellation primitives but no concrete API.
|
||||
|
||||
Cancelled chains do not call `catch` handlers by default. However you can
|
||||
intercept cancellation if you like:
|
||||
|
||||
```swift
|
||||
foo.then {
|
||||
//…
|
||||
}.catch(policy: .allErrors) {
|
||||
// cancelled errors are handled *as well*
|
||||
}
|
||||
```
|
||||
|
||||
**Important**: Canceling a promise chain is *not* the same as canceling the underlying
|
||||
asynchronous task. Promises are wrappers around asynchronicity, but they have no
|
||||
control over the underlying tasks. If you need to cancel an underlying task, you
|
||||
need to cancel the underlying task!
|
||||
|
||||
> The library [CancellablePromiseKit](https://github.com/johannesd/CancellablePromiseKit) extends the concept of Promises to fully cover cancellable tasks.
|
||||
|
||||
## Retry / Polling
|
||||
|
||||
```swift
|
||||
func attempt<T>(maximumRetryCount: Int = 3, delayBeforeRetry: DispatchTimeInterval = .seconds(2), _ body: @escaping () -> Promise<T>) -> Promise<T> {
|
||||
var attempts = 0
|
||||
func attempt() -> Promise<T> {
|
||||
attempts += 1
|
||||
return body().recover { error -> Promise<T> in
|
||||
guard attempts < maximumRetryCount else { throw error }
|
||||
return after(delayBeforeRetry).then(on: nil, attempt)
|
||||
}
|
||||
}
|
||||
return attempt()
|
||||
}
|
||||
|
||||
attempt(maximumRetryCount: 3) {
|
||||
flakeyTask(parameters: foo)
|
||||
}.then {
|
||||
//…
|
||||
}.catch { _ in
|
||||
// we attempted three times but still failed
|
||||
}
|
||||
```
|
||||
|
||||
In most cases, you should probably supplement the code above so that it re-attempts only for
|
||||
specific error conditions.
|
||||
|
||||
|
||||
## Wrapping Delegate Systems
|
||||
|
||||
Be careful with Promises and delegate systems, as they are not always compatible.
|
||||
Promises complete *once*, whereas most delegate systems may notify their delegate many
|
||||
times. This is why, for example, there is no PromiseKit extension for a
|
||||
`UIButton`.
|
||||
|
||||
A good example of an appropriate time to wrap delegation is when you need a
|
||||
single `CLLocation` lookup:
|
||||
|
||||
```swift
|
||||
extension CLLocationManager {
|
||||
static func promise() -> Promise<CLLocation> {
|
||||
return PMKCLLocationManagerProxy().promise
|
||||
}
|
||||
}
|
||||
|
||||
class PMKCLLocationManagerProxy: NSObject, CLLocationManagerDelegate {
|
||||
private let (promise, seal) = Promise<[CLLocation]>.pending()
|
||||
private var retainCycle: PMKCLLocationManagerProxy?
|
||||
private let manager = CLLocationManager()
|
||||
|
||||
init() {
|
||||
super.init()
|
||||
retainCycle = self
|
||||
manager.delegate = self // does not retain hence the `retainCycle` property
|
||||
|
||||
promise.ensure {
|
||||
// ensure we break the retain cycle
|
||||
self.retainCycle = nil
|
||||
}
|
||||
}
|
||||
|
||||
@objc fileprivate func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||
seal.fulfill(locations)
|
||||
}
|
||||
|
||||
@objc func locationManager(_: CLLocationManager, didFailWithError error: Error) {
|
||||
seal.reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
// use:
|
||||
|
||||
CLLocationManager.promise().then { locations in
|
||||
//…
|
||||
}.catch { error in
|
||||
//…
|
||||
}
|
||||
```
|
||||
|
||||
> Please note: we provide this promise with our CoreLocation extensions at
|
||||
> https://github.com/PromiseKit/CoreLocation
|
||||
|
||||
|
||||
## Recovery
|
||||
|
||||
Sometimes you don’t want an error to cascade. Instead, you want to supply a default result:
|
||||
|
||||
```swift
|
||||
CLLocationManager.requestLocation().recover { error -> Promise<CLLocation> in
|
||||
guard error == MyError.airplaneMode else {
|
||||
throw error
|
||||
}
|
||||
return .value(CLLocation.savannah)
|
||||
}.done { location in
|
||||
//…
|
||||
}
|
||||
```
|
||||
|
||||
Be careful not to ignore all errors, though! Recover only those errors that make sense to recover.
|
||||
|
||||
|
||||
## Promises for Modal View Controllers
|
||||
|
||||
```swift
|
||||
class ViewController: UIViewController {
|
||||
|
||||
private let (promise, seal) = Guarantee<…>.pending() // use Promise if your flow can fail
|
||||
|
||||
func show(in: UIViewController) -> Promise<…> {
|
||||
in.show(self, sender: in)
|
||||
return promise
|
||||
}
|
||||
|
||||
func done() {
|
||||
dismiss(animated: true)
|
||||
seal.fulfill(…)
|
||||
}
|
||||
}
|
||||
|
||||
// use:
|
||||
|
||||
ViewController().show(in: self).done {
|
||||
//…
|
||||
}.catch { error in
|
||||
//…
|
||||
}
|
||||
```
|
||||
|
||||
This is the best approach we have found, which is a pity as it requires the
|
||||
presentee to control the presentation and requires the presentee to dismiss itself
|
||||
explicitly.
|
||||
|
||||
Nothing seems to beat storyboard segues for decoupling an app's controllers.
|
||||
|
||||
|
||||
## Saving Previous Results
|
||||
|
||||
Let’s say you have:
|
||||
|
||||
|
||||
```swift
|
||||
login().then { username in
|
||||
fetch(avatar: username)
|
||||
}.done { image in
|
||||
//…
|
||||
}
|
||||
```
|
||||
|
||||
What if you want access to both `username` and `image` in your `done`?
|
||||
|
||||
The most obvious way is to use nesting:
|
||||
|
||||
```swift
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
However, such nesting reduces the clarity of the chain. Instead, we could use Swift
|
||||
tuples:
|
||||
|
||||
```swift
|
||||
login().then { username in
|
||||
fetch(avatar: username).map { ($0, username) }
|
||||
}.then { image, username in
|
||||
//…
|
||||
}
|
||||
```
|
||||
|
||||
The code above simply maps `Promise<String>` into `Promise<(UIImage, String)>`.
|
||||
|
||||
|
||||
## Waiting on Multiple Promises, Whatever Their Result
|
||||
|
||||
Use `when(resolved:)`:
|
||||
|
||||
```swift
|
||||
when(resolved: a, b).done { (results: [Result<T>]) in
|
||||
// `Result` is an enum of `.fulfilled` or `.rejected`
|
||||
}
|
||||
|
||||
// ^^ cannot call `catch` as `when(resolved:)` returns a `Guarantee`
|
||||
```
|
||||
|
||||
Generally, you don't want this! People ask for it a lot, but usually because
|
||||
they are trying to ignore errors. What they really need is to use `recover` on one of the
|
||||
promises. Errors happen, so they should be handled; you usually don't want to ignore them.
|
131
Carthage/Checkouts/PromiseKit/Documentation/Examples/ImageCache.md
vendored
Normal file
131
Carthage/Checkouts/PromiseKit/Documentation/Examples/ImageCache.md
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
# Image Cache with Promises
|
||||
|
||||
Here is an example of a simple image cache that uses promises to simplify the
|
||||
state machine:
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
import PromiseKit
|
||||
|
||||
/**
|
||||
* Small (10 images)
|
||||
* Thread-safe
|
||||
* Consolidates multiple requests to the same URLs
|
||||
* Removes stale entries (FIXME well, strictly we may delete while fetching from cache, but this is unlikely and non-fatal)
|
||||
* Completely _ignores_ server caching headers!
|
||||
*/
|
||||
|
||||
private let q = DispatchQueue(label: "org.promisekit.cache.image")
|
||||
private var active: [URL: Promise<Data>] = [:]
|
||||
private var cleanup = Promise()
|
||||
|
||||
|
||||
public func fetch(image url: URL) -> Promise<Data> {
|
||||
var promise: Promise<Data>?
|
||||
q.sync {
|
||||
promise = active[url]
|
||||
}
|
||||
if let promise = promise {
|
||||
return promise
|
||||
}
|
||||
|
||||
q.sync(flags: .barrier) {
|
||||
promise = Promise(.start) {
|
||||
|
||||
let dst = try url.cacheDestination()
|
||||
|
||||
guard !FileManager.default.isReadableFile(atPath: dst.path) else {
|
||||
return Promise(dst)
|
||||
}
|
||||
|
||||
return Promise { seal in
|
||||
URLSession.shared.downloadTask(with: url) { tmpurl, _, error in
|
||||
do {
|
||||
guard let tmpurl = tmpurl else { throw error ?? E.unexpectedError }
|
||||
try FileManager.default.moveItem(at: tmpurl, to: dst)
|
||||
seal.fulfill(dst)
|
||||
} catch {
|
||||
seal.reject(error)
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
}.then(on: .global(QoS: .userInitiated)) {
|
||||
try Data(contentsOf: $0)
|
||||
}
|
||||
|
||||
active[url] = promise
|
||||
|
||||
if cleanup.isFulfilled {
|
||||
cleanup = promise!.asVoid().then(on: .global(QoS: .utility), execute: docleanup)
|
||||
}
|
||||
}
|
||||
|
||||
return promise!
|
||||
}
|
||||
|
||||
public func cached(image url: URL) -> Data? {
|
||||
guard let dst = try? url.cacheDestination() else {
|
||||
return nil
|
||||
}
|
||||
return try? Data(contentsOf: dst)
|
||||
}
|
||||
|
||||
|
||||
public func cache(destination remoteUrl: URL) throws -> URL {
|
||||
return try remoteUrl.cacheDestination()
|
||||
}
|
||||
|
||||
private func cache() throws -> URL {
|
||||
guard let dst = FileManager.default.docs?
|
||||
.appendingPathComponent("Library")
|
||||
.appendingPathComponent("Caches")
|
||||
.appendingPathComponent("cache.img")
|
||||
else {
|
||||
throw E.unexpectedError
|
||||
}
|
||||
|
||||
try FileManager.default.createDirectory(at: dst, withIntermediateDirectories: true, attributes: [:])
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
private extension URL {
|
||||
func cacheDestination() throws -> URL {
|
||||
|
||||
var fn = String(hashValue)
|
||||
let ext = pathExtension
|
||||
|
||||
// many of Apple's functions don’t recognize file type
|
||||
// unless we preserve the file extension
|
||||
if !ext.isEmpty {
|
||||
fn += ".\(ext)"
|
||||
}
|
||||
|
||||
return try cache().appendingPathComponent(fn)
|
||||
}
|
||||
}
|
||||
|
||||
enum E: Error {
|
||||
case unexpectedError
|
||||
case noCreationTime
|
||||
}
|
||||
|
||||
private func docleanup() throws {
|
||||
var contents = try FileManager.default
|
||||
.contentsOfDirectory(at: try cache(), includingPropertiesForKeys: [.creationDateKey])
|
||||
.map { url -> (Date, URL) in
|
||||
guard let date = try url.resourceValues(forKeys: [.creationDateKey]).creationDate else {
|
||||
throw E.noCreationTime
|
||||
}
|
||||
return (date, url)
|
||||
}.sorted(by: {
|
||||
$0.0 > $1.0
|
||||
})
|
||||
|
||||
while contents.count > 10 {
|
||||
let rm = contents.popLast()!.1
|
||||
try FileManager.default.removeItem(at: rm)
|
||||
}
|
||||
}
|
||||
````
|
20
Carthage/Checkouts/PromiseKit/Documentation/Examples/URLSession+BadResponseErrors.swift
vendored
Normal file
20
Carthage/Checkouts/PromiseKit/Documentation/Examples/URLSession+BadResponseErrors.swift
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
Promise(.pending) { seal in
|
||||
URLSession.shared.dataTask(with: rq, completionHandler: { data, rsp, error in
|
||||
if let data = data {
|
||||
seal.fulfill(data)
|
||||
} else if let error = error {
|
||||
if case URLError.badServerResponse = error, let rsp = rsp as? HTTPURLResponse {
|
||||
seal.reject(Error.badResponse(rsp.statusCode))
|
||||
} else {
|
||||
seal.reject(error)
|
||||
}
|
||||
} else {
|
||||
seal.reject(PMKError.invalidCallingConvention)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
enum Error: Swift.Error {
|
||||
case badUrl
|
||||
case badResponse(Int)
|
||||
}
|
375
Carthage/Checkouts/PromiseKit/Documentation/FAQ.md
vendored
Normal file
375
Carthage/Checkouts/PromiseKit/Documentation/FAQ.md
vendored
Normal file
@@ -0,0 +1,375 @@
|
||||
# FAQ
|
||||
|
||||
## Why should I use PromiseKit over X-Promises-Foo?
|
||||
|
||||
* PromiseKit has a heavy focus on **developer experience**. You’re a developer; do you care about your experience? Yes? Then pick PromiseKit.
|
||||
* Do you care about having any bugs you find fixed? Then pick PromiseKit.
|
||||
* Do you care about having your input heard and reacted to in a fast fashion? Then pick PromiseKit.
|
||||
* Do you want a library that has been maintained continuously and passionately for 6 years? Then pick PromiseKit.
|
||||
* Do you want a library that the community has chosen to be their №1 Promises/Futures library? Then pick PromiseKit.
|
||||
* Do you want to be able to use Promises with Apple’s SDKs rather than having to do all the work of writing the Promise implementations yourself? Then pick PromiseKit.
|
||||
* Do you want to be able to use Promises with Swift 3.x, Swift 4.x, ObjC, iOS, tvOS, watchOS, macOS, Android & Linux? Then pick PromiseKit.
|
||||
* PromiseKit verifies its correctness by testing against the entire [Promises/A+ test suite](https://github.com/promises-aplus/promises-tests).
|
||||
|
||||
## How do I create a fulfilled `Void` promise?
|
||||
|
||||
```swift
|
||||
let foo = Promise()
|
||||
|
||||
// or:
|
||||
|
||||
let bar = Promise.value(())
|
||||
```
|
||||
|
||||
## How do I “early `return`”?
|
||||
|
||||
```swift
|
||||
func foo() -> Promise<Void> {
|
||||
guard thingy else {
|
||||
return Promise()
|
||||
}
|
||||
|
||||
//…
|
||||
}
|
||||
|
||||
func bar() -> Promise<SomethingNotVoid> {
|
||||
guard thingy else {
|
||||
return .value(instanceOfSomethingNotVoid)
|
||||
}
|
||||
|
||||
//…
|
||||
}
|
||||
```
|
||||
|
||||
## Do I need to worry about retain cycles?
|
||||
|
||||
Generally, no. Once a promise completes, all handlers are released and so
|
||||
any references to `self` are also released.
|
||||
|
||||
However, if your chain contains side effects that you would typically
|
||||
not want to happen after, say, a view controller is popped, then you should still
|
||||
use `weak self` (and check for `self == nil`) to prevent any such side effects.
|
||||
|
||||
*However*, in our experience most things that developers consider side effects that
|
||||
should be protected against are in fact *not* side effects.
|
||||
|
||||
Side effects include changes to global application state. They *do not* include
|
||||
changing the display state of a viewController. So, protect against setting UserDefaults or
|
||||
modifying the application database, and don't bother protecting against changing
|
||||
the text in a `UILabel`.
|
||||
|
||||
[This stackoverflow question](https://stackoverflow.com/questions/39281214/should-i-use-weak-self-in-promisekit-blocks)
|
||||
has some good discussion on this topic.
|
||||
|
||||
## Do I need to retain my promises?
|
||||
|
||||
No. Every promise handler retains its promise until the handler is executed. Once
|
||||
all handlers have been executed, the promise is deallocated. So you only need to retain
|
||||
the promise if you need to refer to its final value after its chain has completed.
|
||||
|
||||
## Where should I put my `catch`?
|
||||
|
||||
`catch` deliberately terminates the chain. You should put it low in your promise
|
||||
hierarchy at a point as close to the root as possible. Typically, this would be
|
||||
somewhere such as a view controller, where your `catch` can then display a message
|
||||
to the user.
|
||||
|
||||
This means you should be writing one catch for many `then`s and returning
|
||||
promises that do not have internal `catch` handlers of their own.
|
||||
|
||||
This is obviously a guideline; do what is necessary.
|
||||
|
||||
## How do branched chains work?
|
||||
|
||||
Suppose you have a promise:
|
||||
|
||||
```
|
||||
let promise = foo()
|
||||
```
|
||||
|
||||
And you call `then` twice:
|
||||
|
||||
```
|
||||
promise.then {
|
||||
// branch A
|
||||
}
|
||||
|
||||
promise.then {
|
||||
// branch B
|
||||
}
|
||||
```
|
||||
|
||||
You now have a branched chain. When `promise` resolves, both chains receive its
|
||||
value. However, the two chains are entirely separate and Swift will prompt you
|
||||
to ensure that both have `catch` handlers.
|
||||
|
||||
You can most likely ignore the `catch` for one of these branches, but be careful:
|
||||
in these situations, Swift cannot help you ensure that your chains are error-handled.
|
||||
|
||||
```
|
||||
promise.then {
|
||||
// branch A
|
||||
}.catch { error in
|
||||
//…
|
||||
}
|
||||
|
||||
_ = promise.then {
|
||||
print("foo")
|
||||
|
||||
// ignoring errors here as print cannot error and we handle errors above
|
||||
}
|
||||
```
|
||||
|
||||
It may be safer to recombine the two branches into a single chain again:
|
||||
|
||||
```
|
||||
let p1 = promise.then {
|
||||
// branch A
|
||||
}
|
||||
|
||||
let p2 = promise.then {
|
||||
// branch B
|
||||
}
|
||||
|
||||
when(fulfilled: p1, p2).catch { error in
|
||||
//…
|
||||
}
|
||||
```
|
||||
|
||||
> It's worth noting that you can add multiple `catch` handlers to a promise, too.
|
||||
> And indeed, both will be called if the chain is rejected.
|
||||
|
||||
## Is PromiseKit “heavy”?
|
||||
|
||||
No. PromiseKit contains hardly any source code. In fact, it is quite lightweight. Any
|
||||
“weight” relative to other promise implementations derives from 6 years of bug fixes
|
||||
and tuning, from the fact that we have *stellar* Objective-C-to-Swift bridging and
|
||||
from important things such as [Zalgo prevention](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony)
|
||||
that hobby-project implementations don’t consider.
|
||||
|
||||
## Why is debugging hard?
|
||||
|
||||
Because promises always execute via dispatch, the backtrace you see at the point of
|
||||
an error has less information than is usually required to trace the path of execution.
|
||||
|
||||
One solution is to turn off dispatch during debugging:
|
||||
|
||||
```swift
|
||||
// Swift
|
||||
DispatchQueue.default = zalgo
|
||||
|
||||
//ObjC
|
||||
PMKSetDefaultDispatchQueue(zalgo)
|
||||
```
|
||||
|
||||
Don’t leave this on. In normal use, we always dispatch to avoid you accidentally writing
|
||||
a common bug pattern. See [this blog post](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony).
|
||||
|
||||
## Where is `all()`?
|
||||
|
||||
Some promise libraries provide `all` for awaiting multiple results. We call this function
|
||||
`when`, but it is the same thing. We chose `when` because it's the more common term and
|
||||
because we think it reads better in code.
|
||||
|
||||
## How can I test APIs that return promises?
|
||||
|
||||
You need to use `XCTestExpectation`.
|
||||
|
||||
We also define `wait()` and `hang()`. Use them if you must, but be careful because they
|
||||
block the current thread!
|
||||
|
||||
## Is PromiseKit thread-safe?
|
||||
|
||||
Yes, entirely.
|
||||
|
||||
However the code *you* write in your `then`s might not be!
|
||||
|
||||
Just make sure you don’t access state outside the chain from concurrent queues.
|
||||
By default, PromiseKit handlers run on the `main` thread, which is serial, so
|
||||
you typically won't have to worry about this.
|
||||
|
||||
## Why are there separate classes for Objective-C and Swift?
|
||||
|
||||
`Promise<T>` is generic and and so cannot be represented by Objective-C.
|
||||
|
||||
## Does PromiseKit conform to Promises/A+?
|
||||
|
||||
Yes. We have tests that prove this.
|
||||
|
||||
## How do PromiseKit and RxSwift/ReactiveSwift differ?
|
||||
|
||||
PromiseKit is a lot simpler.
|
||||
|
||||
The top-level difference between PromiseKit and RxSwift is that RxSwift `Observable`s (roughly
|
||||
analogous to PromiseKit `Promise`s) do not necessarily return a single result: they may emit
|
||||
zero, one, or an infinite stream of values. This small conceptual change leads to an API
|
||||
that's both surprisingly powerful and surprisingly complex.
|
||||
|
||||
RxSwift requires commitment to a paradigm shift in how you program. It proposes that you
|
||||
restructure your code as a matrix of interacting value pipelines. When applied properly
|
||||
to a suitable problem, RxSwift can yield great benefits in robustness and simplicity.
|
||||
But not all applications are suitable for RxSwift.
|
||||
|
||||
By contrast, PromiseKit selectively applies the best parts of reactive programming
|
||||
to the hardest part of pure Swift development, the management of asynchrony. It's a broadly
|
||||
applicable tool. Most asynchronous code can be clarified, simplified and made more robust
|
||||
just by converting it to use promises. (And the conversion process is easy.)
|
||||
|
||||
Promises make for code that is clear to most developers. RxSwift, perhaps not. Take a look at this
|
||||
[signup panel](https://github.com/ReactiveX/RxSwift/tree/master/RxExample/RxExample/Examples/GitHubSignup)
|
||||
implemented in RxSwift and see what you think. (Note that this is one of RxSwift's own examples.)
|
||||
|
||||
Even where PromiseKit and RxSwift are broadly similar, there are many differences in implementation:
|
||||
|
||||
* RxSwift has a separate API for chain-terminating elements ("subscribers") versus interior
|
||||
elements. In PromiseKit, all elements of a chain use roughly the same code pattern.
|
||||
|
||||
* The RxSwift API to define an interior element of a chain (an "operator") is hair-raisingly complex.
|
||||
So, RxSwift tries hard to supply every operator you might ever want to use right off the shelf. There are
|
||||
hundreds. PromiseKit supplies a few utilities to help with specific scenarios, but because it's trivial
|
||||
to write your own chain elements, there's no need for all this extra code in the library.
|
||||
|
||||
* PromiseKit dispatches the execution of every block. RxSwift dispatches only when told to do so. Moreover, the
|
||||
current dispatching state is an attribute of the chain, not the specific block, as it is in PromiseKit.
|
||||
The RxSwift system is more powerful but more complex. PromiseKit is simple, predictable and safe.
|
||||
|
||||
* In PromiseKit, both sides of a branched chain refer back to their shared common ancestors. In RxSwift,
|
||||
branching normally creates a duplicate parallel chain that reruns the code at the head of the chain...except
|
||||
when it doesn't. The rules for determining what will actually happen are complex, and given
|
||||
a chain created by another chunk of code, you can't really tell what the behavior will be.
|
||||
|
||||
* Because RxSwift chains don't necessarily terminate on their own, RxSwift needs you to take on some
|
||||
explicit garbage collection duties to ensure that pipelines that are no longer needed are properly
|
||||
deallocated. All promises yield a single value, terminate and then automatically deallocate themselves.
|
||||
|
||||
You can find some additional discussion in [this ticket](https://github.com/mxcl/PromiseKit/issues/484).
|
||||
|
||||
## Why can’t I return from a catch like I can in Javascript?
|
||||
|
||||
Swift demands that functions have one purpose. Thus, we have two error handlers:
|
||||
|
||||
* `catch`: ends the chain and handles errors
|
||||
* `recover`: attempts to recover from errors in a chain
|
||||
|
||||
You want `recover`.
|
||||
|
||||
## When do promises “start”?
|
||||
|
||||
Often people are confused about when Promises “start”. Is it immediately? Is it
|
||||
later? Is it when you call then?
|
||||
|
||||
The answer is: promises do not choose when the underlying task they represent
|
||||
starts. That is up to that task. For example here is the code for a simple
|
||||
promise that wraps Alamofire:
|
||||
|
||||
|
||||
```swift
|
||||
func foo() -> Promise<Any>
|
||||
return Promise { seal in
|
||||
Alamofire.request(rq).responseJSON { rsp in
|
||||
seal.resolve(rsp.value, rsp.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Who chooses when this promise starts? The answer is: Alamofire does, and in this
|
||||
case, it “starts” immediately when `foo()` is called.
|
||||
|
||||
## What is a good way to use Firebase with PromiseKit
|
||||
|
||||
There is no good way to use Firebase with PromiseKit. See the next question for
|
||||
a more detailed rationale.
|
||||
|
||||
The best option is to embed your chain in your Firebase handler:
|
||||
|
||||
```
|
||||
foo.observe(.value) { snapshot in
|
||||
firstly {
|
||||
bar(with: snapshot)
|
||||
}.then {
|
||||
baz()
|
||||
}.then {
|
||||
baffle()
|
||||
}.catch {
|
||||
//…
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## I need my `then` to fire multiple times
|
||||
|
||||
Then we’re afraid you cannot use PromiseKit for that event. Promises only
|
||||
resolve *once*. This is the fundamental nature of promises and it is considered a
|
||||
feature because it gives you guarantees about the flow of your chains.
|
||||
|
||||
|
||||
## How do I change the default queues that handlers run on?
|
||||
|
||||
You can change the values of `PromiseKit.conf.Q`. There are two variables that
|
||||
change the default queues that the two kinds of handler run on. A typical
|
||||
pattern is to change all your `then`-type handlers to run on a background queue
|
||||
and to have all your “finalizers” run on the main queue:
|
||||
|
||||
```
|
||||
PromiseKit.conf.Q.map = .global()
|
||||
PromiseKit.conf.Q.return = .main //NOTE this is the default
|
||||
```
|
||||
|
||||
Be very careful about setting either of these queues to `nil`. It has the
|
||||
effect of running *immediately*, and this is not what you usually want to do in
|
||||
your application. This is, however, useful when you are running specs and want
|
||||
your promises to resolve immediately. (This is basically the same idea as "stubbing"
|
||||
an HTTP request.)
|
||||
|
||||
```swift
|
||||
// in your test suite setup code
|
||||
PromiseKit.conf.Q.map = nil
|
||||
PromiseKit.conf.Q.return = nil
|
||||
```
|
||||
|
||||
## How do I use PromiseKit on the server side?
|
||||
|
||||
If your server framework requires that the main queue remain unused (e.g., Kitura),
|
||||
then you must use PromiseKit 6 and you must tell PromiseKit not to dispatch to the
|
||||
main queue by default. This is easy enough:
|
||||
|
||||
```swift
|
||||
PromiseKit.conf.Q = (map: DispatchQueue.global(), return: DispatchQueue.global())
|
||||
```
|
||||
|
||||
> Note, we recommend using your own queue rather than `.global()`, we've seen better performance this way.
|
||||
|
||||
Here’s a more complete example:
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
import HeliumLogger
|
||||
import Kitura
|
||||
import LoggerAPI
|
||||
import PromiseKit
|
||||
|
||||
HeliumLogger.use(.info)
|
||||
|
||||
let pmkQ = DispatchQueue(label: "pmkQ", qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem)
|
||||
PromiseKit.conf.Q = (map: pmkQ, return: pmkQ)
|
||||
|
||||
let router = Router()
|
||||
router.get("/") { _, response, next in
|
||||
Log.info("Request received")
|
||||
after(seconds: 1.0).done {
|
||||
Log.info("Sending response")
|
||||
response.send("OK")
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
Log.info("Starting server")
|
||||
Kitura.addHTTPServer(onPort: 8888, with: router)
|
||||
Kitura.run()
|
||||
```
|
||||
|
||||
## My question was not answered
|
||||
|
||||
[Please open a ticket](https://github.com/mxcl/PromiseKit/issues/new).
|
535
Carthage/Checkouts/PromiseKit/Documentation/GettingStarted.md
vendored
Normal file
535
Carthage/Checkouts/PromiseKit/Documentation/GettingStarted.md
vendored
Normal file
@@ -0,0 +1,535 @@
|
||||
# `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/
|
193
Carthage/Checkouts/PromiseKit/Documentation/Installation.md
vendored
Normal file
193
Carthage/Checkouts/PromiseKit/Documentation/Installation.md
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
# Xcode 8.3, 9.x or 10.x / Swift 3 or 4
|
||||
|
||||
We recommend Carthage over CocoaPods, but both installation methods are supported.
|
||||
|
||||
## CocoaPods
|
||||
|
||||
```ruby
|
||||
use_frameworks!
|
||||
|
||||
target "Change Me!" do
|
||||
pod "PromiseKit", "~> 6.0"
|
||||
end
|
||||
```
|
||||
|
||||
If the generated Xcode project gives you a warning that PromiseKit needs to be upgraded to
|
||||
Swift 4.0 or Swift 4.2, then add the following:
|
||||
|
||||
```ruby
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
if target.name == 'PromiseKit'
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['SWIFT_VERSION'] = '4.2'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Adjust the value for `SWIFT_VERSION` as needed.
|
||||
|
||||
CocoaPods are aware of this [issue](https://github.com/CocoaPods/CocoaPods/issues/7134).
|
||||
|
||||
## Carthage
|
||||
|
||||
```ruby
|
||||
github "mxcl/PromiseKit" ~> 6.0
|
||||
```
|
||||
|
||||
## SwiftPM
|
||||
|
||||
```ruby
|
||||
package.dependencies.append(
|
||||
.Package(url: "https://github.com/mxcl/PromiseKit", majorVersion: 6)
|
||||
)
|
||||
```
|
||||
|
||||
## Manually
|
||||
|
||||
You can just drop `PromiseKit.xcodeproj` into your project and then add
|
||||
`PromiseKit.framework` to your app’s embedded frameworks.
|
||||
|
||||
|
||||
# PromiseKit vs. Xcode
|
||||
|
||||
PromiseKit contains Swift, so there have been rev-lock issues with Xcode:
|
||||
|
||||
| PromiseKit | Swift | Xcode | CI Status | Release Notes |
|
||||
| ---------- | ----------------------- | -------- | ------------ | ----------------- |
|
||||
| 6 | 3.1, 3.2, 3.3, 4.x | 8.3, 9.x, 10.x | ![ci-master] | [2018/02][news-6] |
|
||||
| 5 | 3.1, 3.2, 3.3, 4.x | 8.3, 9.x | *Deprecated* | *n/a* |
|
||||
| 4† | 3.0, 3.1, 3.2, 3.3, 4.x | 8.x, 9.x | ![ci-master] | [2016/09][news-4] |
|
||||
| 3 | 2.x | 7.x, 8.0 | ![ci-swift2] | [2015/10][news-3] |
|
||||
| 2 | 1.x | 7.x | *Deprecated* | [2015/10][news-3] |
|
||||
| 1‡ | *N/A* | * | ![ci-legacy] | – |
|
||||
|
||||
† Probably supports Xcode 10 and Swift 4.2, if not, PR welcome.
|
||||
|
||||
‡ PromiseKit 1 is pure Objective-C and thus can be used with any Xcode, it is
|
||||
also your only choice if you need to support iOS 7 or below.
|
||||
|
||||
---
|
||||
|
||||
We also maintain a series of branches to aid migration for PromiseKit 2:
|
||||
|
||||
| Xcode | Swift | PromiseKit | Branch | CI Status |
|
||||
| ----- | ----- | -----------| --------------------------- | --------- |
|
||||
| 8.0 | 2.3 | 2 | [swift-2.3-minimal-changes] | ![ci-23] |
|
||||
| 7.3 | 2.2 | 2 | [swift-2.2-minimal-changes] | ![ci-22] |
|
||||
| 7.2 | 2.2 | 2 | [swift-2.2-minimal-changes] | ![ci-22] |
|
||||
| 7.1 | 2.1 | 2 | [swift-2.0-minimal-changes] | ![ci-20] |
|
||||
| 7.0 | 2.0 | 2 | [swift-2.0-minimal-changes] | ![ci-20] |
|
||||
|
||||
We do **not** usually backport fixes to these branches, but pull requests are welcome.
|
||||
|
||||
|
||||
## Xcode 8 / Swift 2.3 or Xcode 7
|
||||
|
||||
```ruby
|
||||
# CocoaPods
|
||||
swift_version = "2.3"
|
||||
pod "PromiseKit", "~> 3.5"
|
||||
|
||||
# Carthage
|
||||
github "mxcl/PromiseKit" ~> 3.5
|
||||
```
|
||||
|
||||
|
||||
[travis]: https://travis-ci.org/mxcl/PromiseKit
|
||||
[ci-master]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=master
|
||||
[ci-legacy]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=legacy-1.x
|
||||
[ci-swift2]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.x
|
||||
[ci-23]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.3-minimal-changes
|
||||
[ci-22]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.2-minimal-changes
|
||||
[ci-20]: https://travis-ci.org/mxcl/PromiseKit.svg?branch=swift-2.0-minimal-changes
|
||||
[news-2]: http://promisekit.org/news/2015/05/PromiseKit-2.0-Released/
|
||||
[news-3]: https://github.com/mxcl/PromiseKit/blob/master/CHANGELOG.markdown#300-oct-1st-2015
|
||||
[news-4]: http://promisekit.org/news/2016/09/PromiseKit-4.0-Released/
|
||||
[news-6]: http://promisekit.org/news/2018/02/PromiseKit-6.0-Released/
|
||||
[swift-2.3-minimal-changes]: https://github.com/mxcl/PromiseKit/tree/swift-2.3-minimal-changes
|
||||
[swift-2.2-minimal-changes]: https://github.com/mxcl/PromiseKit/tree/swift-2.2-minimal-changes
|
||||
[swift-2.0-minimal-changes]: https://github.com/mxcl/PromiseKit/tree/swift-2.0-minimal-changes
|
||||
|
||||
|
||||
# Using Git Submodules for PromiseKit’s Extensions
|
||||
|
||||
> *Note*: This is a more advanced technique.
|
||||
|
||||
If you use CocoaPods and a few PromiseKit extensions, then importing PromiseKit
|
||||
causes that module to import all the extension frameworks. Thus, if you have an
|
||||
app and a few app extensions (e.g., iOS app, iOS watch extension, iOS Today
|
||||
extension) then all your final products that use PromiseKit will have forced
|
||||
dependencies on all the Apple frameworks that PromiseKit provides extensions
|
||||
for.
|
||||
|
||||
This isn’t that bad, but every framework that loads entails overhead and
|
||||
lengthens startup time.
|
||||
|
||||
It’s both better and worse with Carthage. We build individual micro-frameworks
|
||||
for each PromiseKit extension, so your final products link
|
||||
against only the Apple frameworks that they actually need. However, Apple has
|
||||
advised that apps link only against “about 12” frameworks for performance
|
||||
reasons. So with Carthage, we are worse off on this metric.
|
||||
|
||||
The solution is to instead import only CorePromise:
|
||||
|
||||
```ruby
|
||||
# CocoaPods
|
||||
pod "PromiseKit/CorePromise"
|
||||
|
||||
# Carthage
|
||||
github "mxcl/PromiseKit"
|
||||
# ^^ for Carthage *only* have this
|
||||
```
|
||||
|
||||
And to use the extensions you need via `git submodules`:
|
||||
|
||||
```
|
||||
git submodule init
|
||||
git submodule add https://github.com/PromiseKit/UIKit Submodules/PMKUIKit
|
||||
```
|
||||
|
||||
Then in Xcode you can add these sources to your targets on a per-target basis.
|
||||
|
||||
Then when you `pod update`, ensure that you also update your submodules:
|
||||
|
||||
pod update && git submodule update --recursive --remote
|
||||
|
||||
|
||||
|
||||
# Release History
|
||||
|
||||
## [6.0](https://github.com/mxcl/PromiseKit/releases/tag/6.0.0) Feb 13th, 2018
|
||||
|
||||
* [PromiseKit 6 announcement post][news-6].
|
||||
|
||||
## [4.0](https://github.com/mxcl/PromiseKit/releases/tag/4.0.0)
|
||||
|
||||
* [PromiseKit 4 announcement post][news-4].
|
||||
|
||||
## [3.0](https://github.com/mxcl/PromiseKit/releases/tag/3.0.0) Oct 1st, 2015
|
||||
|
||||
In Swift 2.0 `catch` and `defer` became reserved keywords mandating we rename
|
||||
our functions with these names. This forced a major semantic version change on
|
||||
PromiseKit and thus we took the opportunity to make other minor (source
|
||||
compatibility breaking) improvements.
|
||||
|
||||
Thus if you cannot afford to adapt to PromiseKit 3 but still want to use
|
||||
Xcode-7.0/Swift-2.0 we provide a [minimal changes branch] where `catch` and
|
||||
`defer` are renamed `catch_` and `defer_` and all other changes are the bare
|
||||
minimum to make PromiseKit 2 compile against Swift 2.
|
||||
|
||||
If you still are using Xcode 6 and Swift 1.2 then use PromiseKit 2.
|
||||
|
||||
[minimal changes branch]: https://github.com/mxcl/PromiseKit/tree/swift-2.0-minimal-changes
|
||||
|
||||
## [2.0](https://github.com/mxcl/PromiseKit/releases/tag/2.0.0) May 14th, 2015
|
||||
|
||||
[PromiseKit 2 announcement post](http://promisekit.org/news/2015/05/PromiseKit-2.0-Released/).
|
||||
|
||||
## [1.5](https://github.com/mxcl/PromiseKit/releases/tag/1.5.0)
|
||||
|
||||
Swift 1.2 support. Xcode 6.3 required.
|
201
Carthage/Checkouts/PromiseKit/Documentation/ObjectiveC.md
vendored
Normal file
201
Carthage/Checkouts/PromiseKit/Documentation/ObjectiveC.md
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
# Objective-C
|
||||
|
||||
PromiseKit has two promise classes:
|
||||
|
||||
* `Promise<T>` (Swift)
|
||||
* `AnyPromise` (Objective-C)
|
||||
|
||||
Each is designed to be an appropriate promise implementation for the strong points of its language:
|
||||
|
||||
* `Promise<T>` is strict, defined and precise.
|
||||
* `AnyPromise` is loose and dynamic.
|
||||
|
||||
Unlike most libraries, we have extensive bridging support, you can use PromiseKit
|
||||
in mixed projects with mixed language targets and mixed language libraries.
|
||||
|
||||
|
||||
# Using PromiseKit with Objective-C
|
||||
|
||||
`AnyPromise` is our promise class for Objective-C. It behaves almost identically to `Promise<T>`, our Swift promise class.
|
||||
|
||||
```objc
|
||||
myPromise.then(^(NSString *bar){
|
||||
return anotherPromise;
|
||||
}).then(^{
|
||||
//…
|
||||
}).catch(^(NSError *error){
|
||||
//…
|
||||
});
|
||||
```
|
||||
|
||||
You make new promises using `promiseWithResolverBlock`:
|
||||
|
||||
```objc
|
||||
- (AnyPromise *)myPromise {
|
||||
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve){
|
||||
resolve(foo); // if foo is an NSError, rejects, else, resolves
|
||||
}];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
You reject promises by throwing errors:
|
||||
|
||||
```objc
|
||||
myPromise.then(^{
|
||||
@throw [NSError errorWithDomain:domain code:code userInfo:nil];
|
||||
}).catch(^(NSError *error){
|
||||
//…
|
||||
});
|
||||
```
|
||||
#### :warning: Caution:
|
||||
ARC in Objective-C, unlike in Objective-C++, is not exception-safe by default.
|
||||
So, throwing an error will result in keeping a strong reference to the closure
|
||||
that contains the throw statement.
|
||||
This pattern will consequently result in memory leaks if you're not careful.
|
||||
|
||||
> *Note:* Only having a strong reference to the closure would result in memory leaks.
|
||||
> In our case, PromisKit automatically keeps a strong reference to the closure until it's released.
|
||||
|
||||
__Workarounds:__
|
||||
1. Return a Promise with value NSError\
|
||||
Instead of throwing a normal error, you can return a Promise with value NSError instead.
|
||||
|
||||
```objc
|
||||
myPromise.then(^{
|
||||
return [AnyPromise promiseWithValue:[NSError myCustomError]];
|
||||
}).catch(^(NSError *error){
|
||||
if ([error isEqual:[NSError myCustomError]]) {
|
||||
// In case, same error as the one we thrown
|
||||
return;
|
||||
}
|
||||
//…
|
||||
});
|
||||
```
|
||||
2. Enable ARC for exceptions in Objective-C (not recomended)\
|
||||
You can add this ```-fobjc-arc-exceptions to your``` to your compiler flags to enable ARC for exceptions.
|
||||
This is not recommended unless you've read the Apple documentation and are comfortable with the caveats.
|
||||
|
||||
For more details on ARC and exceptions:
|
||||
https://clang.llvm.org/docs/AutomaticReferenceCounting.html#exceptions
|
||||
|
||||
|
||||
---
|
||||
|
||||
One important feature is the syntactic flexability of your handlers:
|
||||
|
||||
```objc
|
||||
myPromise.then(^{
|
||||
// no parameters is fine
|
||||
});
|
||||
|
||||
myPromise.then(^(id foo){
|
||||
// one parameter is fine
|
||||
});
|
||||
|
||||
myPromise.then(^(id a, id b, id c){
|
||||
// up to three parameter is fine, no crash!
|
||||
});
|
||||
|
||||
myPromise.then(^{
|
||||
return @1; // return anything or nothing, it's fine, no crash
|
||||
});
|
||||
```
|
||||
|
||||
We do runtime inspection of the block you pass to achieve this magic.
|
||||
|
||||
---
|
||||
|
||||
Another important distinction is that the equivalent function to Swift’s `recover` is combined with `AnyPromise`’s `catch`. This is typical to other “dynamic” promise implementations and thus achieves our goal that `AnyPromise` is loose and dynamic while `Promise<T>` is strict and specific.
|
||||
|
||||
A sometimes unexpected consequence of this fact is that returning nothing from a `catch` *resolves* the returned promise:
|
||||
|
||||
```objc
|
||||
myPromise.catch(^{
|
||||
[UIAlertView …];
|
||||
}).then(^{
|
||||
// always executes!
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Another important distinction is that the `value` property returns even if the promise is rejected; in that case, it returns the `NSError` object with which the promise was rejected.
|
||||
|
||||
|
||||
# Bridging Between Objective-C & Swift
|
||||
|
||||
Let’s say you have:
|
||||
|
||||
```objc
|
||||
@interface Foo
|
||||
- (AnyPromise *)myPromise;
|
||||
@end
|
||||
```
|
||||
|
||||
Ensure that this interface is included in your bridging header. You can now use the
|
||||
following pattern in your Swift code:
|
||||
|
||||
```swift
|
||||
let foo = Foo()
|
||||
foo.myPromise.then { (obj: AnyObject?) -> Int in
|
||||
// it is not necessary to specify the type of `obj`
|
||||
// we just do that for demonstrative purposes
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Let’s say you have:
|
||||
|
||||
```swift
|
||||
@objc class Foo: NSObject {
|
||||
func stringPromise() -> Promise<String>
|
||||
func barPromise() -> Promise<Bar>
|
||||
}
|
||||
|
||||
@objc class Bar: NSObject { /*…*/ }
|
||||
```
|
||||
|
||||
Ensure that your project is generating a `…-Swift.h` header so that Objective-C can see your Swift code.
|
||||
|
||||
If you built this project and opened the `…-Swift.h` header, you would only see this:
|
||||
|
||||
```objc
|
||||
@interface Foo
|
||||
@end
|
||||
|
||||
@interface Bar
|
||||
@end
|
||||
```
|
||||
|
||||
That's because Objective-C cannot import Swift objects that are generic. So we need to write some stubs:
|
||||
|
||||
```swift
|
||||
@objc class Foo: NSObject {
|
||||
@objc func stringPromise() -> AnyPromise {
|
||||
return AnyPromise(stringPromise())
|
||||
}
|
||||
@objc func barPromise() -> AnyPromise {
|
||||
return AnyPromise(barPromise())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If we built this and opened our generated header, we would now see:
|
||||
|
||||
```objc
|
||||
@interface Foo
|
||||
- (AnyPromise *)stringPromise;
|
||||
- (AnyPromise *)barPromise;
|
||||
@end
|
||||
|
||||
@interface Bar
|
||||
@end
|
||||
```
|
||||
|
||||
Perfect.
|
||||
|
||||
Note that AnyPromise can only bridge objects that conform to `AnyObject` or derive from `NSObject`. This is a limitation of Objective-C.
|
||||
|
13
Carthage/Checkouts/PromiseKit/Documentation/README.md
vendored
Normal file
13
Carthage/Checkouts/PromiseKit/Documentation/README.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Contents
|
||||
|
||||
* [README](/README.md)
|
||||
* Handbook
|
||||
* [Getting Started](GettingStarted.md)
|
||||
* [Promises: Common Patterns](CommonPatterns.md)
|
||||
* [Frequently Asked Questions](FAQ.md)
|
||||
* Manual
|
||||
* [Installation Guide](Installation.md)
|
||||
* [Objective-C Guide](ObjectiveC.md)
|
||||
* [Troubleshooting](Troubleshooting.md)
|
||||
* [Appendix](Appendix.md)
|
||||
* [API Reference](https://promisekit.org/reference/)
|
260
Carthage/Checkouts/PromiseKit/Documentation/Troubleshooting.md
vendored
Normal file
260
Carthage/Checkouts/PromiseKit/Documentation/Troubleshooting.md
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
# Troubleshooting
|
||||
|
||||
## Compilation errors
|
||||
|
||||
99% of compilation issues involving PromiseKit can be addressed or diagnosed by one of the fixes below.
|
||||
|
||||
### Check your handler
|
||||
|
||||
```swift
|
||||
return firstly {
|
||||
URLSession.shared.dataTask(.promise, with: url)
|
||||
}.compactMap {
|
||||
JSONSerialization.jsonObject(with: $0.data) as? [String: Any]
|
||||
}.then { dict in
|
||||
User(dict: dict)
|
||||
}
|
||||
```
|
||||
|
||||
Swift (unhelpfully) says:
|
||||
|
||||
> Cannot convert value of type '([String : Any]) -> User' to expected argument type '([String : Any]) -> _'
|
||||
|
||||
What’s the real problem? `then` *must* return a `Promise`, and you're trying to return something else. What you really want is `map`:
|
||||
|
||||
```swift
|
||||
return firstly {
|
||||
URLSession.shared.dataTask(.promise, with: url)
|
||||
}.compactMap {
|
||||
JSONSerialization.jsonObject(with: $0.data) as? [String: Any]
|
||||
}.map { dict in
|
||||
User(dict: dict)
|
||||
}
|
||||
```
|
||||
|
||||
### Specify closure parameters **and** return type
|
||||
|
||||
For example:
|
||||
|
||||
```swift
|
||||
return firstly {
|
||||
foo()
|
||||
}.then { user in
|
||||
//…
|
||||
return bar()
|
||||
}
|
||||
```
|
||||
|
||||
This code may compile if you specify the type of `user`:
|
||||
|
||||
|
||||
```swift
|
||||
return firstly {
|
||||
foo()
|
||||
}.then { (user: User) in
|
||||
//…
|
||||
return bar()
|
||||
}
|
||||
```
|
||||
|
||||
If it still doesn't compile, perhaps you need to specify the return type, too:
|
||||
|
||||
```swift
|
||||
return firstly {
|
||||
foo()
|
||||
}.then { (user: User) -> Promise<Bar> in
|
||||
//…
|
||||
return bar()
|
||||
}
|
||||
```
|
||||
|
||||
We have made great effort to reduce the need for explicit typing in PromiseKit 6,
|
||||
but as with all Swift functions that return a generic type (e.g., `Array.map`),
|
||||
you may need to explicitly tell Swift what a closure returns if the closure's body is
|
||||
longer than one line.
|
||||
|
||||
> *Tip*: Sometimes you can force a one-liner by using semicolons.
|
||||
|
||||
|
||||
### Acknowledge all incoming closure parameters
|
||||
|
||||
Swift does not permit you to silently ignore a closure's parameters. For example, this code:
|
||||
|
||||
```swift
|
||||
func _() -> Promise<Void> {
|
||||
return firstly {
|
||||
proc.launch(.promise) // proc: Foundation.Process
|
||||
}.then {
|
||||
when(fulfilled: p1, p2) // both p1 & p2 are `Promise<Void>`
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Fails to compile with the error:
|
||||
|
||||
Cannot invoke 'then' with an argument list of type '(() -> _)
|
||||
|
||||
What's the problem? Well, `Process.launch(.promise)` returns
|
||||
`Promise<(String, String)>`, and we are ignoring this value in our `then` closure.
|
||||
If we’d referenced `$0` or named the parameter, Swift would have been satisfied.
|
||||
|
||||
Assuming that we really do want to ignore the argument, the fix is to explicitly
|
||||
acknowledge its existence by assigning it the name "_". That's Swift-ese for "I
|
||||
know there's a value here, but I'm ignoring it."
|
||||
|
||||
|
||||
```swift
|
||||
func _() -> Promise<Void> {
|
||||
return firstly {
|
||||
proc.launch(.promise)
|
||||
}.then { _ in
|
||||
when(fulfilled: p1, p2)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this situation, you won't always receive an error message that's as clear as the
|
||||
one shown above. Sometimes, a missing closure parameter sends Swift scurrying off
|
||||
into type inference limbo. When it finally concludes that there's no way for it to make
|
||||
all the inferred types work together, it may end up assigning blame to some other
|
||||
closure entirely and giving you an error message that makes no sense at all.
|
||||
|
||||
When faced with this kind of enigmatic complaint, a good rule of thumb is to
|
||||
double-check your argument and return types carefully. If everything looks OK,
|
||||
temporarily add explicit type information as shown above, just to rule
|
||||
out mis-inference as a possible cause.
|
||||
|
||||
### Try moving code to a temporary inline function
|
||||
|
||||
Try taking the code out of a closure and putting it in a standalone function. Now Swift
|
||||
will give you the *real* error message. For example:
|
||||
|
||||
```swift
|
||||
func doStuff() {
|
||||
firstly {
|
||||
foo()
|
||||
}.then {
|
||||
let bar = bar()
|
||||
let baz = baz()
|
||||
when(fulfilled: bar, baz)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```swift
|
||||
func doStuff() {
|
||||
func fluff() -> Promise<…> {
|
||||
let bar = bar()
|
||||
let baz = baz()
|
||||
when(fulfilled: bar, baz)
|
||||
}
|
||||
|
||||
firstly {
|
||||
foo()
|
||||
}.then {
|
||||
fluff()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
An *inline* function like this is all you need. Here, the problem is that you
|
||||
forgot to mark the last line of the closure with an explicit `return`. It's required
|
||||
here because the closure is longer than one line.
|
||||
|
||||
|
||||
## You copied code off the Internet that doesn’t work
|
||||
|
||||
Swift has changed a lot over the years and so PromiseKit has had to change to keep
|
||||
up. The code you copied is probably for an older PromiseKit. *Read the definitions of the
|
||||
functions.* It's easy to do this in Xcode by option-clicking or command-clicking function names.
|
||||
All PromiseKit functions are documented and provide examples.
|
||||
|
||||
## "Context type for closure argument expects 1 argument, which cannot be implicitly ignored"
|
||||
|
||||
You have a `then`; you want a `done`.
|
||||
|
||||
## "Missing argument for parameter #1 in call"
|
||||
|
||||
This is part of Swift 4’s “tuplegate”.
|
||||
|
||||
You must specify your `Void` parameter:
|
||||
|
||||
```swift
|
||||
seal.fulfill(())
|
||||
```
|
||||
|
||||
Yes: we hope they revert this change in Swift 5 too.
|
||||
|
||||
## Other issues
|
||||
|
||||
### `Pending Promise Deallocated!`
|
||||
|
||||
If you see this warning, you have a path in your `Promise` initializer that allows
|
||||
the promise to escape without being sealed:
|
||||
|
||||
```swift
|
||||
Promise<String> { seal in
|
||||
task { value, error in
|
||||
if let value = value as? String {
|
||||
seal.fulfill(value)
|
||||
} else if let error = error {
|
||||
seal.reject(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are two missing paths here, and if either occurs, the promise will soon be
|
||||
deallocated without resolving. This will manifest itself as a bug in your app,
|
||||
probably the awful infinite spinner.
|
||||
|
||||
So let’s be thorough:
|
||||
|
||||
```swift
|
||||
Promise<String> { seal in
|
||||
task { value, error in
|
||||
if let value = value as? String {
|
||||
fulfill(value)
|
||||
} else if let error = error {
|
||||
reject(error)
|
||||
} else if value != nil {
|
||||
reject(MyError.valueNotString)
|
||||
} else {
|
||||
// should never happen, but we have an `PMKError` for task being called with `nil`, `nil`
|
||||
reject(PMKError.invalidCallingConvention)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If this seems tedious, it shouldn’t. You would have to be this thorough without promises, too.
|
||||
The difference is that without promises, you wouldn’t get a warning in the console notifying
|
||||
you of your mistake!
|
||||
|
||||
### Slow compilation / compiler cannot solve in reasonable time
|
||||
|
||||
Add return types to your closures.
|
||||
|
||||
### My promise never resolves
|
||||
|
||||
Check to be sure that your asynchronous task even *starts*. You’d be surprised how
|
||||
often this is the cause.
|
||||
|
||||
For example, if you are using `URLSession` without our extension (but
|
||||
don’t do that; *use* our extension! we know all the pitfalls), did you forget
|
||||
to call `resume` on the task? If so, the task never actually starts, and so of
|
||||
course it never finishes, either.
|
||||
|
||||
### `Result of call to 'done(on:_:)' is unused`, `Result of call to 'then(on:_:)' is unused`
|
||||
|
||||
PromiseKit deliberately avoids the `@discardableResult` annotation because the
|
||||
unused result warning is a hint that you have not handled the error in your
|
||||
chain. So do one of these:
|
||||
|
||||
1. Add a `catch`
|
||||
2. `return` the promise (thus punting the error handling to the caller)
|
||||
3. Use `cauterize()` to silence the warning.
|
||||
|
||||
Obviously, do 1 or 2 in preference to 3.
|
Reference in New Issue
Block a user