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,424 @@
import Dispatch
/// Thenable represents an asynchronous operation that can be chained.
public protocol Thenable: class {
/// The type of the wrapped value
associatedtype T
/// `pipe` is immediately executed when this `Thenable` is resolved
func pipe(to: @escaping(Result<T>) -> Void)
/// The resolved result or nil if pending.
var result: Result<T>? { get }
}
public extension Thenable {
/**
The provided closure executes when this promise resolves.
This 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 body: The closure that executes when this promise fulfills. It must return a promise.
- Returns: A new promise that resolves when the promise returned from the provided closure resolves. For example:
firstly {
URLSession.shared.dataTask(.promise, with: url1)
}.then { response in
transform(data: response.data)
}.done { transformation in
//
}
*/
func then<U: Thenable>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> U) -> Promise<U.T> {
let rp = Promise<U.T>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async(flags: flags) {
do {
let rv = try body(value)
guard rv !== rp else { throw PMKError.returnedSelf }
rv.pipe(to: rp.box.seal)
} catch {
rp.box.seal(.rejected(error))
}
}
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
/**
The provided closure is executed when this promise is resolved.
This is like `then` but it requires the closure to return a non-promise.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter transform: The closure that is executed when this Promise is fulfilled. It must return a non-promise.
- Returns: A new promise that is resolved with the value returned from the provided closure. For example:
firstly {
URLSession.shared.dataTask(.promise, with: url1)
}.map { response in
response.data.length
}.done { length in
//
}
*/
func map<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U) -> Promise<U> {
let rp = Promise<U>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async(flags: flags) {
do {
rp.box.seal(.fulfilled(try transform(value)))
} catch {
rp.box.seal(.rejected(error))
}
}
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
/**
The provided closure is executed when this promise is resolved.
In your closure return an `Optional`, if you return `nil` the resulting promise is rejected with `PMKError.compactMap`, otherwise the promise is fulfilled with the unwrapped value.
firstly {
URLSession.shared.dataTask(.promise, with: url)
}.compactMap {
try JSONSerialization.jsonObject(with: $0.data) as? [String: String]
}.done { dictionary in
//
}.catch {
// either `PMKError.compactMap` or a `JSONError`
}
*/
func compactMap<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U?) -> Promise<U> {
let rp = Promise<U>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async(flags: flags) {
do {
if let rv = try transform(value) {
rp.box.seal(.fulfilled(rv))
} else {
throw PMKError.compactMap(value, U.self)
}
} catch {
rp.box.seal(.rejected(error))
}
}
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
/**
The provided closure is executed when this promise is resolved.
Equivalent to `map { x -> Void in`, but since we force the `Void` return Swift
is happier and gives you less hassle about your closures qualification.
- 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 fulfilled as `Void`.
firstly {
URLSession.shared.dataTask(.promise, with: url)
}.done { response in
print(response.data)
}
*/
func done(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> Void) -> Promise<Void> {
let rp = Promise<Void>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async(flags: flags) {
do {
try body(value)
rp.box.seal(.fulfilled(()))
} catch {
rp.box.seal(.rejected(error))
}
}
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
/**
The provided closure is executed when this promise is resolved.
This is like `done` but it returns the same value that the handler is fed.
`get` immutably accesses the fulfilled value; the returned Promise maintains that value.
- 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 that the handler is fed. For example:
firstly {
.value(1)
}.get { foo in
print(foo, " is 1")
}.done { foo in
print(foo, " is 1")
}.done { foo in
print(foo, " is Void")
}
*/
func get(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) throws -> Void) -> Promise<T> {
return map(on: on, flags: flags) {
try body($0)
return $0
}
}
/**
The provided closure is executed with promise result.
This is like `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.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The closure that is executed with Result of Promise.
- Returns: A new promise that is resolved with the result that the handler is fed. For example:
promise.tap{ print($0) }.then{ /**/ }
*/
func tap(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Result<T>) -> Void) -> Promise<T> {
return Promise { seal in
pipe { result in
on.async(flags: flags) {
body(result)
seal.resolve(result)
}
}
}
}
/// - Returns: a new promise chained off this promise but with its value discarded.
func asVoid() -> Promise<Void> {
return map(on: nil) { _ in }
}
}
public extension Thenable {
/**
- Returns: The error with which this promise was rejected; `nil` if this promise is not rejected.
*/
var error: Error? {
switch result {
case .none:
return nil
case .some(.fulfilled):
return nil
case .some(.rejected(let error)):
return error
}
}
/**
- Returns: `true` if the promise has not yet resolved.
*/
var isPending: Bool {
return result == nil
}
/**
- Returns: `true` if the promise has resolved.
*/
var isResolved: Bool {
return !isPending
}
/**
- Returns: `true` if the promise was fulfilled.
*/
var isFulfilled: Bool {
return value != nil
}
/**
- Returns: `true` if the promise was rejected.
*/
var isRejected: Bool {
return error != nil
}
/**
- Returns: The value with which this promise was fulfilled or `nil` if this promise is pending or rejected.
*/
var value: T? {
switch result {
case .none:
return nil
case .some(.fulfilled(let value)):
return value
case .some(.rejected):
return nil
}
}
}
public extension Thenable where T: Sequence {
/**
`Promise<[T]>` => `T` -> `U` => `Promise<[U]>`
firstly {
.value([1,2,3])
}.mapValues { integer in
integer * 2
}.done {
// $0 => [2,4,6]
}
*/
func mapValues<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> {
return map(on: on, flags: flags){ try $0.map(transform) }
}
/**
`Promise<[T]>` => `T` -> `[U]` => `Promise<[U]>`
firstly {
.value([1,2,3])
}.flatMapValues { integer in
[integer, integer]
}.done {
// $0 => [1,1,2,2,3,3]
}
*/
func flatMapValues<U: Sequence>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> {
return map(on: on, flags: flags){ (foo: T) in
try foo.flatMap{ try transform($0) }
}
}
/**
`Promise<[T]>` => `T` -> `U?` => `Promise<[U]>`
firstly {
.value(["1","2","a","3"])
}.compactMapValues {
Int($0)
}.done {
// $0 => [1,2,3]
}
*/
func compactMapValues<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> {
return map(on: on, flags: flags) { foo -> [U] in
#if !swift(>=3.3) || (swift(>=4) && !swift(>=4.1))
return try foo.flatMap(transform)
#else
return try foo.compactMap(transform)
#endif
}
}
/**
`Promise<[T]>` => `T` -> `Promise<U>` => `Promise<[U]>`
firstly {
.value([1,2,3])
}.thenMap { integer in
.value(integer * 2)
}.done {
// $0 => [2,4,6]
}
*/
func thenMap<U: Thenable>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> {
return then(on: on, flags: flags) {
when(fulfilled: try $0.map(transform))
}
}
/**
`Promise<[T]>` => `T` -> `Promise<[U]>` => `Promise<[U]>`
firstly {
.value([1,2,3])
}.thenFlatMap { integer in
.value([integer, integer])
}.done {
// $0 => [1,1,2,2,3,3]
}
*/
func thenFlatMap<U: Thenable>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T.Iterator.Element]> where U.T: Sequence {
return then(on: on, flags: flags) {
when(fulfilled: try $0.map(transform))
}.map(on: nil) {
$0.flatMap{ $0 }
}
}
/**
`Promise<[T]>` => `T` -> Bool => `Promise<[U]>`
firstly {
.value([1,2,3])
}.filterValues {
$0 > 1
}.done {
// $0 => [2,3]
}
*/
func filterValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ isIncluded: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> {
return map(on: on, flags: flags) {
$0.filter(isIncluded)
}
}
}
public extension Thenable where T: Collection {
/// - Returns: a promise fulfilled with the first value of this `Collection` or, if empty, a promise rejected with PMKError.emptySequence.
var firstValue: Promise<T.Iterator.Element> {
return map(on: nil) { aa in
if let a1 = aa.first {
return a1
} else {
throw PMKError.emptySequence
}
}
}
func firstValue(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, where test: @escaping (T.Iterator.Element) -> Bool) -> Promise<T.Iterator.Element> {
return map(on: on, flags: flags) {
for x in $0 where test(x) {
return x
}
throw PMKError.emptySequence
}
}
/// - Returns: a promise fulfilled with the last value of this `Collection` or, if empty, a promise rejected with PMKError.emptySequence.
var lastValue: Promise<T.Iterator.Element> {
return map(on: nil) { aa in
if aa.isEmpty {
throw PMKError.emptySequence
} else {
let i = aa.index(aa.endIndex, offsetBy: -1)
return aa[i]
}
}
}
}
public extension Thenable where T: Sequence, T.Iterator.Element: Comparable {
/// - Returns: a promise fulfilled with the sorted values of this `Sequence`.
func sortedValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil) -> Promise<[T.Iterator.Element]> {
return map(on: on, flags: flags){ $0.sorted() }
}
}