202 lines
5.6 KiB
Swift
202 lines
5.6 KiB
Swift
import class Foundation.Thread
|
||
import Dispatch
|
||
|
||
/**
|
||
A `Guarantee` is a functional abstraction around an asynchronous operation that cannot error.
|
||
- See: `Thenable`
|
||
*/
|
||
public class Guarantee<T>: Thenable {
|
||
let box: Box<T>
|
||
|
||
fileprivate init(box: SealedBox<T>) {
|
||
self.box = box
|
||
}
|
||
|
||
/// Returns a `Guarantee` sealed with the provided value.
|
||
public static func value(_ value: T) -> Guarantee<T> {
|
||
return .init(box: SealedBox(value: value))
|
||
}
|
||
|
||
/// Returns a pending `Guarantee` that can be resolved with the provided closure’s parameter.
|
||
public init(resolver body: (@escaping(T) -> Void) -> Void) {
|
||
box = EmptyBox()
|
||
body(box.seal)
|
||
}
|
||
|
||
/// - See: `Thenable.pipe`
|
||
public func pipe(to: @escaping(Result<T>) -> Void) {
|
||
pipe{ to(.fulfilled($0)) }
|
||
}
|
||
|
||
func pipe(to: @escaping(T) -> Void) {
|
||
switch box.inspect() {
|
||
case .pending:
|
||
box.inspect {
|
||
switch $0 {
|
||
case .pending(let handlers):
|
||
handlers.append(to)
|
||
case .resolved(let value):
|
||
to(value)
|
||
}
|
||
}
|
||
case .resolved(let value):
|
||
to(value)
|
||
}
|
||
}
|
||
|
||
/// - See: `Thenable.result`
|
||
public var result: Result<T>? {
|
||
switch box.inspect() {
|
||
case .pending:
|
||
return nil
|
||
case .resolved(let value):
|
||
return .fulfilled(value)
|
||
}
|
||
}
|
||
|
||
init(_: PMKUnambiguousInitializer) {
|
||
box = EmptyBox()
|
||
}
|
||
|
||
/// Returns a tuple of a pending `Guarantee` and a function that resolves it.
|
||
public class func pending() -> (guarantee: Guarantee<T>, resolve: (T) -> Void) {
|
||
return { ($0, $0.box.seal) }(Guarantee<T>(.pending))
|
||
}
|
||
}
|
||
|
||
public extension Guarantee {
|
||
@discardableResult
|
||
func done(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Void) -> Guarantee<Void> {
|
||
let rg = Guarantee<Void>(.pending)
|
||
pipe { (value: T) in
|
||
on.async(flags: flags) {
|
||
body(value)
|
||
rg.box.seal(())
|
||
}
|
||
}
|
||
return rg
|
||
}
|
||
|
||
func get(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) -> Void) -> Guarantee<T> {
|
||
return map(on: on, flags: flags) {
|
||
body($0)
|
||
return $0
|
||
}
|
||
}
|
||
|
||
func map<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> U) -> Guarantee<U> {
|
||
let rg = Guarantee<U>(.pending)
|
||
pipe { value in
|
||
on.async(flags: flags) {
|
||
rg.box.seal(body(value))
|
||
}
|
||
}
|
||
return rg
|
||
}
|
||
|
||
@discardableResult
|
||
func then<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Guarantee<U>) -> Guarantee<U> {
|
||
let rg = Guarantee<U>(.pending)
|
||
pipe { value in
|
||
on.async(flags: flags) {
|
||
body(value).pipe(to: rg.box.seal)
|
||
}
|
||
}
|
||
return rg
|
||
}
|
||
|
||
public func asVoid() -> Guarantee<Void> {
|
||
return map(on: nil) { _ in }
|
||
}
|
||
|
||
/**
|
||
Blocks this thread, so you know, don’t call this on a serial thread that
|
||
any part of your chain may use. Like the main thread for example.
|
||
*/
|
||
public func wait() -> T {
|
||
|
||
if Thread.isMainThread {
|
||
print("PromiseKit: warning: `wait()` called on main thread!")
|
||
}
|
||
|
||
var result = value
|
||
|
||
if result == nil {
|
||
let group = DispatchGroup()
|
||
group.enter()
|
||
pipe { (foo: T) in result = foo; group.leave() }
|
||
group.wait()
|
||
}
|
||
|
||
return result!
|
||
}
|
||
}
|
||
|
||
public extension Guarantee where T: Sequence {
|
||
|
||
/**
|
||
`Guarantee<[T]>` => `T` -> `Guarantee<U>` => `Guaranetee<[U]>`
|
||
|
||
firstly {
|
||
.value([1,2,3])
|
||
}.thenMap {
|
||
.value($0 * 2)
|
||
}.done {
|
||
// $0 => [2,4,6]
|
||
}
|
||
*/
|
||
func thenMap<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) -> Guarantee<U>) -> Guarantee<[U]> {
|
||
return then(on: on, flags: flags) {
|
||
when(fulfilled: $0.map(transform))
|
||
}.recover {
|
||
// if happens then is bug inside PromiseKit
|
||
fatalError(String(describing: $0))
|
||
}
|
||
}
|
||
}
|
||
|
||
#if swift(>=3.1)
|
||
public extension Guarantee where T == Void {
|
||
convenience init() {
|
||
self.init(box: SealedBox(value: Void()))
|
||
}
|
||
}
|
||
#endif
|
||
|
||
|
||
public extension DispatchQueue {
|
||
/**
|
||
Asynchronously executes the provided closure on a dispatch queue.
|
||
|
||
DispatchQueue.global().async(.promise) {
|
||
md5(input)
|
||
}.done { md5 in
|
||
//…
|
||
}
|
||
|
||
- Parameter body: The closure that resolves this promise.
|
||
- Returns: A new `Guarantee` resolved by the result of the provided closure.
|
||
- Note: There is no Promise/Thenable version of this due to Swift compiler ambiguity issues.
|
||
*/
|
||
@available(macOS 10.10, iOS 2.0, tvOS 10.0, watchOS 2.0, *)
|
||
final func async<T>(_: PMKNamespacer, group: DispatchGroup? = nil, qos: DispatchQoS = .default, flags: DispatchWorkItemFlags = [], execute body: @escaping () -> T) -> Guarantee<T> {
|
||
let rg = Guarantee<T>(.pending)
|
||
async(group: group, qos: qos, flags: flags) {
|
||
rg.box.seal(body())
|
||
}
|
||
return rg
|
||
}
|
||
}
|
||
|
||
|
||
#if os(Linux)
|
||
import func CoreFoundation._CFIsMainThread
|
||
|
||
extension Thread {
|
||
// `isMainThread` is not implemented yet in swift-corelibs-foundation.
|
||
static var isMainThread: Bool {
|
||
return _CFIsMainThread()
|
||
}
|
||
}
|
||
#endif
|