PromiseKit/Sources/Promise.swift
Max Howell 6bab5e0c7f Add @discardableResult; Closes #595; Tag 4.5.0
PromiseKit 5 much improves this area with the concept of an unavailable promise (`Guarantee`).

Thus since we offer a superior library now, we will close this ticket.
2017-11-05 17:52:08 -05:00

647 lines
25 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import class Dispatch.DispatchQueue
import class Foundation.NSError
import func Foundation.NSLog
/**
A *promise* represents the future value of a (usually) asynchronous task.
To obtain the value of a promise we call `then`.
Promises are chainable: `then` returns a promise, you can call `then` on
that promise, which returns a promise, you can call `then` on that
promise, et cetera.
Promises start in a pending state and *resolve* with a value to become
*fulfilled* or an `Error` to become rejected.
- SeeAlso: [PromiseKit `then` Guide](http://promisekit.org/docs/)
*/
open class Promise<T> {
let state: State<T>
/**
Create a new, pending promise.
func fetchAvatar(user: String) -> Promise<UIImage> {
return Promise { fulfill, reject in
MyWebHelper.GET("\(user)/avatar") { data, err in
guard let data = data else { return reject(err) }
guard let img = UIImage(data: data) else { return reject(MyError.InvalidImage) }
guard let img.size.width > 0 else { return reject(MyError.ImageTooSmall) }
fulfill(img)
}
}
}
- Parameter resolvers: The provided closure is called immediately on the active thread; commence your asynchronous task, calling either fulfill or reject when it completes.
- Parameter fulfill: Fulfills this promise with the provided value.
- Parameter reject: Rejects this promise with the provided error.
- Returns: A new promise.
- Note: If you are wrapping a delegate-based system, we recommend
to use instead: `Promise.pending()`
- SeeAlso: http://promisekit.org/docs/sealing-promises/
- SeeAlso: http://promisekit.org/docs/cookbook/wrapping-delegation/
- SeeAlso: pending()
*/
required public init(resolvers: (_ fulfill: @escaping (T) -> Void, _ reject: @escaping (Error) -> Void) throws -> Void) {
var resolve: ((Resolution<T>) -> Void)!
do {
state = UnsealedState(resolver: &resolve)
try resolvers({ resolve(.fulfilled($0)) }, { error in
#if !PMKDisableWarnings
if self.isPending {
resolve(Resolution(error))
} else {
NSLog("PromiseKit: warning: reject called on already rejected Promise: \(error)")
}
#else
resolve(Resolution(error))
#endif
})
} catch {
resolve(Resolution(error))
}
}
/**
Create an already fulfilled promise.
To create a resolved `Void` promise, do: `Promise(value: ())`
*/
required public init(value: T) {
state = SealedState(resolution: .fulfilled(value))
}
/**
Create an already rejected promise.
*/
required public init(error: Error) {
state = SealedState(resolution: Resolution(error))
}
/**
Careful with this, it is imperative that sealant can only be called once
or you will end up with spurious unhandled-errors due to possible double
rejections and thus immediately deallocated ErrorConsumptionTokens.
*/
init(sealant: (@escaping (Resolution<T>) -> Void) -> Void) {
var resolve: ((Resolution<T>) -> Void)!
state = UnsealedState(resolver: &resolve)
sealant(resolve)
}
/**
A `typealias` for the return values of `pending()`. Simplifies declaration of properties that reference the values' containing tuple when this is necessary. For example, when working with multiple `pendingPromise(value: ())`s within the same scope, or when the promise initialization must occur outside of the caller's initialization.
class Foo: BarDelegate {
var task: Promise<Int>.PendingTuple?
}
- SeeAlso: pending()
*/
public typealias PendingTuple = (promise: Promise, fulfill: (T) -> Void, reject: (Error) -> Void)
/**
Making promises that wrap asynchronous delegation systems or other larger asynchronous systems without a simple completion handler is easier with pending.
class Foo: BarDelegate {
let (promise, fulfill, reject) = Promise<Int>.pending()
func barDidFinishWithResult(result: Int) {
fulfill(result)
}
func barDidError(error: NSError) {
reject(error)
}
}
- Returns: A tuple consisting of:
1) A promise
2) A function that fulfills that promise
3) A function that rejects that promise
*/
public final class func pending() -> PendingTuple {
var fulfill: ((T) -> Void)!
var reject: ((Error) -> Void)!
let promise = self.init { fulfill = $0; reject = $1 }
return (promise, fulfill, reject)
}
/**
The provided closure is executed when this promise is resolved.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The closure that is executed when this Promise is fulfilled.
- Returns: A new promise that is resolved with the value returned from the provided closure. For example:
URLSession.GET(url).then { data -> Int in
//
return data.length
}.then { length in
//
}
*/
@discardableResult
public func then<U>(on q: DispatchQueue = .default, execute body: @escaping (T) throws -> U) -> Promise<U> {
return Promise<U> { resolve in
state.then(on: q, else: resolve) { value in
resolve(.fulfilled(try body(value)))
}
}
}
/**
The provided closure executes when this promise resolves.
This variant of `then` allows chaining promises, the promise returned by the provided closure is resolved before the promise returned by this closure resolves.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter execute: The closure that executes when this promise fulfills.
- Returns: A new promise that resolves when the promise returned from the provided closure resolves. For example:
URLSession.GET(url1).then { data in
return CLLocationManager.promise()
}.then { location in
//
}
*/
@discardableResult
public func then<U>(on q: DispatchQueue = .default, execute body: @escaping (T) throws -> Promise<U>) -> Promise<U> {
var resolve: ((Resolution<U>) -> Void)!
let rv = Promise<U>{ resolve = $0 }
state.then(on: q, else: resolve) { value in
let promise = try body(value)
guard promise !== rv else { throw PMKError.returnedSelf }
promise.state.pipe(resolve)
}
return rv
}
/**
The provided closure executes when this promise resolves.
This variant of `then` allows returning a tuple of promises within provided closure. All of the returned
promises needs be fulfilled for this promise to be marked as resolved.
- Note: At maximum 5 promises may be returned in a tuple
- Note: If *any* of the tuple-provided promises reject, the returned promise is immediately rejected with that error.
- Warning: In the event of rejection the other promises will continue to resolve and, as per any other promise, will either fulfill or reject.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter execute: The closure that executes when this promise fulfills.
- Returns: A new promise that resolves when all promises returned from the provided closure resolve. For example:
loginPromise.then { _ -> (Promise<Data>, Promise<UIImage>)
return (URLSession.GET(userUrl), URLSession.dataTask(with: avatarUrl).asImage())
}.then { userData, avatarImage in
//
}
*/
@discardableResult
public func then<U, V>(on q: DispatchQueue = .default, execute body: @escaping (T) throws -> (Promise<U>, Promise<V>)) -> Promise<(U, V)> {
return then(on: q, execute: body) { when(fulfilled: $0.0, $0.1) }
}
/// This variant of `then` allows returning a tuple of promises within provided closure.
@discardableResult
public func then<U, V, X>(on q: DispatchQueue = .default, execute body: @escaping (T) throws -> (Promise<U>, Promise<V>, Promise<X>)) -> Promise<(U, V, X)> {
return then(on: q, execute: body) { when(fulfilled: $0.0, $0.1, $0.2) }
}
/// This variant of `then` allows returning a tuple of promises within provided closure.
@discardableResult
public func then<U, V, X, Y>(on q: DispatchQueue = .default, execute body: @escaping (T) throws -> (Promise<U>, Promise<V>, Promise<X>, Promise<Y>)) -> Promise<(U, V, X, Y)> {
return then(on: q, execute: body) { when(fulfilled: $0.0, $0.1, $0.2, $0.3) }
}
/// This variant of `then` allows returning a tuple of promises within provided closure.
@discardableResult
public func then<U, V, X, Y, Z>(on q: DispatchQueue = .default, execute body: @escaping (T) throws -> (Promise<U>, Promise<V>, Promise<X>, Promise<Y>, Promise<Z>)) -> Promise<(U, V, X, Y, Z)> {
return then(on: q, execute: body) { when(fulfilled: $0.0, $0.1, $0.2, $0.3, $0.4) }
}
/// utility function to serve `then` implementations with `body` returning tuple of promises
@discardableResult
private func then<U, V>(on q: DispatchQueue, execute body: @escaping (T) throws -> V, when: @escaping (V) -> Promise<U>) -> Promise<U> {
return Promise<U> { resolve in
state.then(on: q, else: resolve) { value in
let promise = try body(value)
// since when(promise) switches to `zalgo`, we have to pipe back to `q`
when(promise).state.pipe(on: q, to: resolve)
}
}
}
/**
The provided closure executes when this promise rejects.
Rejecting a promise cascades: rejecting all subsequent promises (unless
recover is invoked) thus you will typically place your catch at the end
of a chain. Often utility promises will not have a catch, instead
delegating the error handling to the caller.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter execute: The handler to execute if this promise is rejected.
- Returns: `self`
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
- Important: The promise that is returned is `self`. `catch` cannot affect the chain, in PromiseKit 3 no promise was returned to strongly imply this, however for PromiseKit 4 we started returning a promise so that you can `always` after a catch or return from a function that has an error handler.
*/
@discardableResult
public func `catch`(on q: DispatchQueue = .default, policy: CatchPolicy = .allErrorsExceptCancellation, execute body: @escaping (Error) -> Void) -> Promise {
state.catch(on: q, policy: policy, else: { _ in }, execute: body)
return self
}
/**
The provided closure executes when this promise rejects.
Unlike `catch`, `recover` continues the chain provided the closure does not throw. Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
CLLocationManager.promise().recover { error in
guard error == CLError.unknownLocation else { throw error }
return CLLocation.Chicago
}
- Parameter on: The queue to which the provided closure dispatches.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter execute: The handler to execute if this promise is rejected.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
@discardableResult
public func recover(on q: DispatchQueue = .default, policy: CatchPolicy = .allErrorsExceptCancellation, execute body: @escaping (Error) throws -> Promise) -> Promise {
var resolve: ((Resolution<T>) -> Void)!
let rv = Promise{ resolve = $0 }
state.catch(on: q, policy: policy, else: resolve) { error in
let promise = try body(error)
guard promise !== rv else { throw PMKError.returnedSelf }
promise.state.pipe(resolve)
}
return rv
}
/**
The provided closure executes when this promise rejects.
Unlike `catch`, `recover` continues the chain provided the closure does not throw. Use `recover` in circumstances where recovering the chain from certain errors is a possibility. For example:
CLLocationManager.promise().recover { error in
guard error == CLError.unknownLocation else { throw error }
return CLLocation.Chicago
}
- Parameter on: The queue to which the provided closure dispatches.
- Parameter policy: The default policy does not execute your handler for cancellation errors.
- Parameter execute: The handler to execute if this promise is rejected.
- SeeAlso: [Cancellation](http://promisekit.org/docs/)
*/
@discardableResult
public func recover(on q: DispatchQueue = .default, policy: CatchPolicy = .allErrorsExceptCancellation, execute body: @escaping (Error) throws -> T) -> Promise {
return Promise { resolve in
state.catch(on: q, policy: policy, else: resolve) { error in
resolve(.fulfilled(try body(error)))
}
}
}
/**
The provided closure executes when this promise resolves.
firstly {
UIApplication.shared.networkActivityIndicatorVisible = true
}.then {
//
}.always {
UIApplication.shared.networkActivityIndicatorVisible = false
}.catch {
//
}
- Parameter on: The queue to which the provided closure dispatches.
- Parameter execute: The closure that executes when this promise resolves.
- Returns: A new promise, resolved with this promises resolution.
*/
@discardableResult
public func always(on q: DispatchQueue = .default, execute body: @escaping () -> Void) -> Promise {
state.always(on: q) { resolution in
body()
}
return self
}
/**
Allows you to tap into a promise chain and inspect its result.
The function you provide cannot mutate the chain.
URLSession.GET(/**/).tap { result in
print(result)
}
- Parameter on: The queue to which the provided closure dispatches.
- Parameter execute: The closure that executes when this promise resolves.
- Returns: A new promise, resolved with this promises resolution.
*/
@discardableResult
public func tap(on q: DispatchQueue = .default, execute body: @escaping (Result<T>) -> Void) -> Promise {
state.always(on: q) { resolution in
body(Result(resolution))
}
return self
}
/**
Void promises are less prone to generics-of-doom scenarios.
- SeeAlso: when.swift contains enlightening examples of using `Promise<Void>` to simplify your code.
*/
public func asVoid() -> Promise<Void> {
return then(on: zalgo) { _ in return }
}
//MARK: deprecations
@available(*, unavailable, renamed: "always()")
public func finally(on: DispatchQueue = DispatchQueue.main, execute body: () -> Void) -> Promise { fatalError() }
@available(*, unavailable, renamed: "always()")
public func ensure(on: DispatchQueue = DispatchQueue.main, execute body: () -> Void) -> Promise { fatalError() }
@available(*, unavailable, renamed: "pending()")
public class func `defer`() -> PendingTuple { fatalError() }
@available(*, unavailable, renamed: "pending()")
public class func `pendingPromise`() -> PendingTuple { fatalError() }
@available(*, unavailable, message: "deprecated: use then(on: .global())")
public func thenInBackground<U>(execute body: (T) throws -> U) -> Promise<U> { fatalError() }
@available(*, unavailable, renamed: "catch")
public func onError(policy: CatchPolicy = .allErrors, execute body: (Error) -> Void) { fatalError() }
@available(*, unavailable, renamed: "catch")
public func errorOnQueue(_ on: DispatchQueue, policy: CatchPolicy = .allErrors, execute body: (Error) -> Void) { fatalError() }
@available(*, unavailable, renamed: "catch")
public func error(policy: CatchPolicy, execute body: (Error) -> Void) { fatalError() }
@available(*, unavailable, renamed: "catch")
public func report(policy: CatchPolicy = .allErrors, execute body: (Error) -> Void) { fatalError() }
@available(*, unavailable, renamed: "init(value:)")
public init(_ value: T) { fatalError() }
//MARK: disallow `Promise<Error>`
@available(*, unavailable, message: "cannot instantiate Promise<Error>")
public init<T: Error>(resolvers: (_ fulfill: (T) -> Void, _ reject: (Error) -> Void) throws -> Void) { fatalError() }
@available(*, unavailable, message: "cannot instantiate Promise<Error>")
public class func pending<T: Error>() -> (promise: Promise, fulfill: (T) -> Void, reject: (Error) -> Void) { fatalError() }
//MARK: disallow returning `Error`
@available (*, unavailable, message: "instead of returning the error; throw")
public func then<U: Error>(on: DispatchQueue = .default, execute body: (T) throws -> U) -> Promise<U> { fatalError() }
@available (*, unavailable, message: "instead of returning the error; throw")
public func recover<T: Error>(on: DispatchQueue = .default, execute body: (Error) throws -> T) -> Promise { fatalError() }
//MARK: disallow returning `Promise?`
@available(*, unavailable, message: "unwrap the promise")
public func then<U>(on: DispatchQueue = .default, execute body: (T) throws -> Promise<U>?) -> Promise<U> { fatalError() }
@available(*, unavailable, message: "unwrap the promise")
public func recover(on: DispatchQueue = .default, execute body: (Error) throws -> Promise?) -> Promise { fatalError() }
}
extension Promise: CustomStringConvertible {
public var description: String {
return "Promise: \(state)"
}
}
/**
Judicious use of `firstly` *may* make chains more readable.
Compare:
URLSession.GET(url1).then {
URLSession.GET(url2)
}.then {
URLSession.GET(url3)
}
With:
firstly {
URLSession.GET(url1)
}.then {
URLSession.GET(url2)
}.then {
URLSession.GET(url3)
}
*/
public func firstly<T>(execute body: () throws -> Promise<T>) -> Promise<T> {
return firstly(execute: body) { $0 }
}
/**
Judicious use of `firstly` *may* make chains more readable.
Firstly allows to return tuple of promises
Compare:
when(fulfilled: URLSession.GET(url1), URLSession.GET(url2)).then {
URLSession.GET(url3)
}.then {
URLSession.GET(url4)
}
With:
firstly {
(URLSession.GET(url1), URLSession.GET(url2))
}.then { _, _ in
URLSession.GET(url2)
}.then {
URLSession.GET(url3)
}
- Note: At maximum 5 promises may be returned in a tuple
- Note: If *any* of the tuple-provided promises reject, the returned promise is immediately rejected with that error.
*/
public func firstly<T, U>(execute body: () throws -> (Promise<T>, Promise<U>)) -> Promise<(T, U)> {
return firstly(execute: body) { when(fulfilled: $0.0, $0.1) }
}
/// Firstly allows to return tuple of promises
public func firstly<T, U, V>(execute body: () throws -> (Promise<T>, Promise<U>, Promise<V>)) -> Promise<(T, U, V)> {
return firstly(execute: body) { when(fulfilled: $0.0, $0.1, $0.2) }
}
/// Firstly allows to return tuple of promises
public func firstly<T, U, V, W>(execute body: () throws -> (Promise<T>, Promise<U>, Promise<V>, Promise<W>)) -> Promise<(T, U, V, W)> {
return firstly(execute: body) { when(fulfilled: $0.0, $0.1, $0.2, $0.3) }
}
/// Firstly allows to return tuple of promises
public func firstly<T, U, V, W, X>(execute body: () throws -> (Promise<T>, Promise<U>, Promise<V>, Promise<W>, Promise<X>)) -> Promise<(T, U, V, W, X)> {
return firstly(execute: body) { when(fulfilled: $0.0, $0.1, $0.2, $0.3, $0.4) }
}
/// utility function to serve `firstly` implementations with `body` returning tuple of promises
fileprivate func firstly<U, V>(execute body: () throws -> V, when: (V) -> Promise<U>) -> Promise<U> {
do {
return when(try body())
} catch {
return Promise(error: error)
}
}
@available(*, unavailable, message: "instead of returning the error; throw")
public func firstly<T: Error>(execute body: () throws -> T) -> Promise<T> { fatalError() }
@available(*, unavailable, message: "use DispatchQueue.promise")
public func firstly<T>(on: DispatchQueue, execute body: () throws -> Promise<T>) -> Promise<T> { fatalError() }
/**
- SeeAlso: `DispatchQueue.promise(group:qos:flags:execute:)`
*/
@available(*, deprecated: 4.0, renamed: "DispatchQueue.promise")
public func dispatch_promise<T>(_ on: DispatchQueue, _ body: @escaping () throws -> T) -> Promise<T> {
return Promise(value: ()).then(on: on, execute: body)
}
/**
The underlying resolved state of a promise.
- Remark: Same as `Resolution<T>` but without the associated `ErrorConsumptionToken`.
*/
public enum Result<T> {
/// Fulfillment
case fulfilled(T)
/// Rejection
case rejected(Error)
init(_ resolution: Resolution<T>) {
switch resolution {
case .fulfilled(let value):
self = .fulfilled(value)
case .rejected(let error, _):
self = .rejected(error)
}
}
/**
- Returns: `true` if the result is `fulfilled` or `false` if it is `rejected`.
*/
public var boolValue: Bool {
switch self {
case .fulfilled:
return true
case .rejected:
return false
}
}
}
/**
An object produced by `Promise.joint()`, along with a promise to which it is bound.
Joining with a promise via `Promise.join(_:)` will pipe the resolution of that promise to
the joint's bound promise.
- SeeAlso: `Promise.joint()`
- SeeAlso: `Promise.join(_:)`
*/
public class PMKJoint<T> {
fileprivate var resolve: ((Resolution<T>) -> Void)!
}
extension Promise {
/**
Provides a safe way to instantiate a `Promise` and resolve it later via its joint and another
promise.
class Engine {
static func make() -> Promise<Engine> {
let (enginePromise, joint) = Promise<Engine>.joint()
let cylinder: Cylinder = Cylinder(explodeAction: {
// We *could* use an IUO, but there are no guarantees about when
// this callback will be called. Having an actual promise is safe.
enginePromise.then { engine in
engine.checkOilPressure()
}
})
firstly {
Ignition.default.start()
}.then { plugs in
Engine(cylinders: [cylinder], sparkPlugs: plugs)
}.join(joint)
return enginePromise
}
}
- Returns: A new promise and its joint.
- SeeAlso: `Promise.join(_:)`
*/
public final class func joint() -> (Promise<T>, PMKJoint<T>) {
let pipe = PMKJoint<T>()
let promise = Promise(sealant: { pipe.resolve = $0 })
return (promise, pipe)
}
/**
Pipes the value of this promise to the promise created with the joint.
- Parameter joint: The joint on which to join.
- SeeAlso: `Promise.joint()`
*/
public func join(_ joint: PMKJoint<T>) {
state.pipe(joint.resolve)
}
}
extension Promise where T: Collection {
/**
Transforms a `Promise` where `T` is a `Collection` into a `Promise<[U]>`
URLSession.shared.dataTask(url: /**/).asArray().map { result in
return download(result)
}.then { images in
// images is `[UIImage]`
}
- Parameter on: The queue to which the provided closure dispatches.
- Parameter transform: The closure that executes when this promise resolves.
- Returns: A new promise, resolved with this promises resolution.
*/
public func map<U>(on: DispatchQueue = .default, transform: @escaping (T.Iterator.Element) throws -> Promise<U>) -> Promise<[U]> {
return Promise<[U]> { resolve in
return state.then(on: zalgo, else: resolve) { tt in
when(fulfilled: try tt.map(transform)).state.pipe(resolve)
}
}
}
}
#if swift(>=3.1)
public extension Promise where T == Void {
convenience init() {
self.init(value: ())
}
}
#endif