Add PromiseKit dependency

- Added PromiseKit dependency
This commit is contained in:
2018-11-15 22:08:00 -04:00
parent 2689d86c18
commit be7b6b5881
541 changed files with 46282 additions and 0 deletions

View File

@@ -0,0 +1,211 @@
# Common Misusage
## Doubling up Promises
Dont 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 dont 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 UIKits
completion API supplies a `Bool`. However, we usually dont 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 dont have to care what queue your functions run on.