PromiseKit/Sources/Guarantee.swift
Doug 2b90cb3d4e
'Cancel' for PromiseKit (#899)
This patch adds the concept of a `CancelContext` to PromiseKit allowing chains to handle cancellation properly for each independent promise after the cancel point.
2019-04-07 16:25:31 -04:00

284 lines
7.9 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 Foundation.Thread
import Dispatch
/**
A `Guarantee` is a functional abstraction around an asynchronous operation that cannot error.
- See: `Thenable`
*/
public final class Guarantee<T>: Thenable {
let box: PromiseKit.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 closures parameter.
public init(resolver body: (@escaping(T) -> Void) -> Void) {
box = Box()
body(box.seal)
}
/// Returns a pending `Guarantee` that can be resolved with the provided closures parameter.
public convenience init(cancellable: Cancellable, resolver body: (@escaping(T) -> Void) -> Void) {
self.init(resolver: body)
setCancellable(cancellable)
}
/// - See: `Thenable.pipe`
public func pipe(to: @escaping(Result<T, Error>) -> Void) {
pipe{ to(.success($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, Error>? {
switch box.inspect() {
case .pending:
return nil
case .resolved(let value):
return .success(value)
}
}
final private class Box<T>: EmptyBox<T> {
var cancelled = false
deinit {
switch inspect() {
case .pending:
if !cancelled {
PromiseKit.conf.logHandler(.pendingGuaranteeDeallocated)
}
case .resolved:
break
}
}
}
init(_: PMKUnambiguousInitializer) {
box = Box()
}
/// 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))
}
var cancellable: Cancellable?
public func setCancellable(_ cancellable: Cancellable) {
if let gb = (box as? Guarantee<T>.Box<T>) {
self.cancellable = CancellableWrapper(box: gb, cancellable: cancellable)
} else {
self.cancellable = cancellable
}
}
final private class CancellableWrapper: Cancellable {
let box: Guarantee<T>.Box<T>
let cancellable: Cancellable
init(box: Guarantee<T>.Box<T>, cancellable: Cancellable) {
self.box = box
self.cancellable = cancellable
}
func cancel() {
box.cancelled = true
cancellable.cancel()
}
var isCancelled: Bool {
return cancellable.isCancelled
}
}
}
public extension Guarantee {
@discardableResult
func done(on: Dispatcher = conf.D.return, _ body: @escaping(T) -> Void) -> Guarantee<Void> {
let rg = Guarantee<Void>(.pending)
pipe { (value: T) in
on.dispatch {
body(value)
rg.box.seal(())
}
}
return rg
}
func get(on: Dispatcher = conf.D.return, _ body: @escaping (T) -> Void) -> Guarantee<T> {
return map(on: on) {
body($0)
return $0
}
}
func map<U>(on: Dispatcher = conf.D.map, _ body: @escaping(T) -> U) -> Guarantee<U> {
let rg = Guarantee<U>(.pending)
pipe { value in
on.dispatch {
rg.box.seal(body(value))
}
}
return rg
}
@discardableResult
func then<U>(on: Dispatcher = conf.D.map, _ body: @escaping(T) -> Guarantee<U>) -> Guarantee<U> {
let rg = Guarantee<U>(.pending)
pipe { value in
on.dispatch {
body(value).pipe(to: rg.box.seal)
}
}
return rg
}
func asVoid() -> Guarantee<Void> {
return map(on: nil) { _ in }
}
/**
Blocks this thread, so you know, dont call this on a serial thread that
any part of your chain may use. Like the main thread for example.
*/
func wait() -> T {
if Thread.isMainThread {
conf.logHandler(.waitOnMainThread)
}
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>` => `Guarantee<[U]>`
firstly {
.value([1,2,3])
}.thenMap {
.value($0 * 2)
}.done {
// $0 => [2,4,6]
}
*/
func thenMap<U>(on: Dispatcher = conf.D.map, _ transform: @escaping(T.Iterator.Element) -> Guarantee<U>) -> Guarantee<[U]> {
return then(on: on) {
when(fulfilled: $0.map(transform))
}.recover {
// if happens then is bug inside PromiseKit
fatalError(String(describing: $0))
}
}
}
public extension Guarantee where T == Void {
convenience init() {
self.init(box: SealedBox(value: Void()))
}
#if swift(>=5.1)
// ^^ ambiguous in Swift 5.0, testing again in next version
convenience init(resolver body: (@escaping() -> Void) -> Void) {
self.init(resolver: { seal in
body {
seal(())
}
})
}
#endif
}
public extension DispatchQueue {
/**
Asynchronously executes the provided closure on a dispatch queue, yielding a `Guarantee`.
DispatchQueue.global().async(.promise) {
md5(input)
}.done { md5 in
//
}
- _: Must be `.promise` to distinguish from standard `DispatchQueue.async`
- group: A `DispatchGroup`, as for standard `DispatchQueue.async`
- qos: A quality-of-service grade, as for standard `DispatchQueue.async`
- flags: Work item flags, as for standard `DispatchQueue.async`
- body: A closure that yields a value to resolve the guarantee.
- Returns: A new `Guarantee` resolved by the result of the provided closure.
*/
@available(macOS 10.10, iOS 2.0, tvOS 10.0, watchOS 2.0, *)
final func async<T>(_: PMKNamespacer, group: DispatchGroup? = nil, qos: DispatchQoS? = nil, flags: DispatchWorkItemFlags? = nil, execute body: @escaping () -> T) -> Guarantee<T> {
let rg = Guarantee<T>(.pending)
asyncD(group: group, qos: qos, flags: flags) {
rg.box.seal(body())
}
return rg
}
}
public extension Dispatcher {
/**
Executes the provided closure on a `Dispatcher`, yielding a `Guarantee`
that represents the value ultimately returned by the closure.
dispatcher.dispatch {
md5(input)
}.done { md5 in
//
}
- Parameter body: The closure that yields the value of the Guarantee.
- Returns: A new `Guarantee` resolved by the result of the provided closure.
*/
func dispatch<T>(_ body: @escaping () -> T) -> Guarantee<T> {
let rg = Guarantee<T>(.pending)
dispatch {
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