Created
January 21, 2017 19:32
-
-
Save mxcl/c8fcb063d81be0a7a0914047ca509642 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
// Caveats (specify fixes alongside) | |
// * Promise { throw E.dummy } is interpreted as `Promise<() throws -> Void>` of all things | |
// * Promise(E.dummy) is interpreted as `Promise<E>` | |
// Remarks: | |
// * We typically use `.pending()` to reduce nested insanities in your backtraces | |
public protocol Thenable: class { | |
associatedtype T | |
/// - Remark: Ideally would be `then` but the return-type proved tricky | |
func pipe(to: @escaping (Result<T>) -> Void) | |
var result: Result<T>? { get } | |
} | |
public protocol Catchable: Thenable | |
{} | |
enum Schrödinger<R> { | |
case pending(Handlers<R>) | |
case resolved(R) | |
} | |
public enum Result<T> { | |
case rejected(Error) | |
case fulfilled(T) | |
} | |
class Handlers<R> { | |
var bodies: [(R) -> Void] = [] | |
} | |
public struct Sealant<T> { | |
public let resolve: (Result<T>) -> Void | |
public func fulfill(_ value: T) { | |
resolve(.fulfilled(value)) | |
} | |
public func reject(_ error: Error) { | |
resolve(.rejected(error)) | |
} | |
} | |
public enum UnambiguousInitializer { | |
case start | |
} | |
public final class Promise<T>: Catchable, Thenable { | |
/// - Remark: It is possible to create a `Promise<Error>` with this method. But please don’t. We tried to make this prohobited, but Swift doesn’t seem able to obey our unavailable annotations in this contest yet. | |
public init(value: T) { | |
schrödinger = .resolved(.fulfilled(value)) | |
} | |
public init(error: Error) { | |
schrödinger = .resolved(.rejected(error)) | |
} | |
fileprivate var schrödinger: Schrödinger<Result<T>> { | |
didSet { | |
guard case .pending(let handlers) = oldValue, case .resolved(let result) = schrödinger else { | |
schrödinger = oldValue | |
return | |
} | |
for body in handlers.bodies { | |
body(result) | |
} | |
} | |
} | |
public init(seal body: (Sealant<T>) throws -> Void) { | |
do { | |
schrödinger = .pending(Handlers()) | |
try body(Sealant{ self.schrödinger = .resolved($0) }) | |
} catch { | |
schrödinger = .resolved(.rejected(error)) | |
} | |
} | |
public init(_: UnambiguousInitializer, assimilate body: () throws -> Promise) { | |
do { | |
schrödinger = try body().schrödinger | |
} catch { | |
schrödinger = .resolved(.rejected(error)) | |
} | |
} | |
public func pipe(to body: @escaping (Result<T>) -> Void) { | |
switch schrödinger { | |
case .pending(let handlers): | |
handlers.bodies.append(body) | |
case .resolved(let result): | |
body(result) | |
} | |
} | |
public var result: Result<T>? { | |
switch schrödinger { | |
case .pending: | |
return nil | |
case .resolved(let result): | |
return result | |
} | |
} | |
fileprivate init() { | |
schrödinger = .pending(Handlers()) | |
} | |
public static func pending() -> (promise: Promise, seal: Sealant<T>) { | |
let promise = Promise() | |
let sealant = Sealant{ promise.schrödinger = .resolved($0) } | |
return (promise, sealant) | |
} | |
public func asVoid() -> Promise<Void> { | |
return then{ _ in } | |
} | |
} | |
extension Thenable { | |
public func then<U: Thenable>(qos: DispatchQoS? = nil, execute body: @escaping (T) throws -> U) -> Promise<U.T> { | |
let promise = Promise<U.T>() | |
pipe { result in | |
switch result { | |
case .fulfilled(let value): | |
qos.async { | |
do { | |
try body(value).pipe{ promise.schrödinger = .resolved($0) } | |
} catch { | |
promise.schrödinger = .resolved(.rejected(error)) | |
} | |
} | |
case .rejected(let error): | |
promise.schrödinger = .resolved(.rejected(error)) | |
} | |
} | |
return promise | |
} | |
public func then<U>(qos: DispatchQoS? = nil, execute body: @escaping (T) throws -> U) -> Promise<U> { | |
let promise = Promise<U>() | |
pipe { result in | |
switch result { | |
case .fulfilled(let value): | |
qos.async { | |
do { | |
let value = try body(value) | |
promise.schrödinger = .resolved(.fulfilled(value)) | |
} catch { | |
promise.schrödinger = .resolved(.rejected(error)) | |
} | |
} | |
case .rejected(let error): | |
promise.schrödinger = .resolved(.rejected(error)) | |
} | |
} | |
return promise | |
} | |
} | |
extension Catchable { | |
public func always(qos: DispatchQoS? = nil, execute body: @escaping () -> Void) -> Self { | |
pipe { _ in | |
qos.async(execute: body) | |
} | |
return self | |
} | |
@discardableResult | |
public func `catch`(handler body: @escaping (Error) -> Void) -> Finally { | |
let finally = Finally() | |
pipe { result in | |
switch result { | |
case .fulfilled: | |
break | |
case .rejected(let error): | |
body(error) | |
} | |
finally.schrödinger = .resolved() | |
} | |
return finally | |
} | |
public var error: Error? { | |
switch result { | |
case .rejected(let error)?: | |
return error | |
case .fulfilled?, nil: | |
return nil | |
} | |
} | |
} | |
public class Finally { | |
fileprivate var schrödinger: Schrödinger<Void> = .pending(Handlers()) { | |
didSet { | |
guard case .pending(let handlers) = oldValue else { fatalError() } | |
for handler in handlers.bodies { | |
handler() | |
} | |
} | |
} | |
@discardableResult | |
public func finally(execute body: @escaping () -> Void) -> Finally { | |
switch schrödinger { | |
case .pending(let handlers): | |
handlers.bodies.append(body) | |
case .resolved: | |
body() | |
} | |
return self | |
} | |
} | |
private func unwrap(_ any: Any?) -> Result<Any?> { | |
if let error = any as? Error { | |
return .rejected(error) | |
} else { | |
return .fulfilled(any) | |
} | |
} | |
@objc(AnyPromise) | |
public class AnyPromise: NSObject, Thenable { | |
fileprivate var schrödinger: Schrödinger<Any?> { | |
didSet { | |
guard case .pending(let handlers) = oldValue, case .resolved(let value) = schrödinger else { | |
schrödinger = oldValue | |
return | |
} | |
for body in handlers.bodies { | |
body(value) | |
} | |
} | |
} | |
public var result: Result<Any?>? { | |
switch schrödinger { | |
case .resolved(let value): | |
return unwrap(value) | |
case .pending: | |
return nil | |
} | |
} | |
public func pipe(to body: @escaping (Result<Any?>) -> Void) { | |
let body = { body(unwrap($0)) } | |
switch schrödinger { | |
case .pending(let handlers): | |
handlers.bodies.append(body) | |
case .resolved(let value): | |
body(value) | |
} | |
} | |
public override init() { | |
schrödinger = .resolved(nil) | |
} | |
} | |
/** - Remark: much like a real-life guarantee, it is only as reliable as the source; “promises” | |
may never resolve, it is up to the thing providing you the promise to ensure that they do. | |
Generally it is considered bad programming for a promise provider to provide a promise that | |
never resolves. In real life a guarantee may not be met by eg. World War III, so think | |
similarly. | |
*/ | |
public final class Guarantee<T>: Thenable { | |
/// - Remark: `Guarantee()` thus creates a resolved `Void` Guarantee. | |
public init(_ value: T) { | |
schrödinger = .resolved(value) | |
} | |
public init(sealant body: (@escaping (T) -> Void) -> Void) { | |
schrödinger = .pending(Handlers()) | |
body { self.schrödinger = .resolved($0) } | |
} | |
private init(schrödinger: Schrödinger<T>) { | |
self.schrödinger = schrödinger | |
} | |
public static func pending() -> (Guarantee<T>, (T) -> Void) { | |
let g = Guarantee<T>(schrödinger: .pending(Handlers())) | |
return (g, { g.schrödinger = .resolved($0) }) | |
} | |
fileprivate var schrödinger: Schrödinger<T> { | |
didSet { | |
guard case .pending(let handlers) = oldValue, case .resolved(let value) = schrödinger else { | |
schrödinger = oldValue | |
return | |
} | |
for body in handlers.bodies { | |
body(value) | |
} | |
} | |
} | |
public func pipe(to body: @escaping (Result<T>) -> Void) { | |
__pipe{ body(.fulfilled($0)) } | |
} | |
public func __pipe(to body: @escaping (T) -> Void) { | |
switch schrödinger { | |
case .pending(let handlers): | |
handlers.bodies.append(body) | |
case .resolved(let value): | |
body(value) | |
} | |
} | |
public var result: Result<T>? { | |
switch schrödinger { | |
case .pending: | |
return nil | |
case .resolved(let value): | |
return .fulfilled(value) | |
} | |
} | |
@discardableResult | |
public func then<U>(qos: DispatchQoS? = nil, execute body: @escaping (T) -> Guarantee<U>) -> Guarantee<U> { | |
let (guarantee, _) = Guarantee<U>.pending() | |
__pipe { value in | |
qos.async { | |
guarantee.schrödinger = body(value).schrödinger | |
} | |
} | |
return guarantee | |
} | |
@discardableResult | |
public func then<U>(qos: DispatchQoS? = nil, execute body: @escaping (T) -> U) -> Guarantee<U> { | |
let (guarantee, _) = Guarantee<U>.pending() | |
__pipe { value in | |
qos.async { | |
guarantee.schrödinger = .resolved(body(value)) | |
} | |
} | |
return guarantee | |
} | |
} | |
public func after(interval: TimeInterval) -> Guarantee<Void> { | |
let guarantee = Guarantee<Void>() | |
defer { | |
DispatchQueue.global().asyncAfter(deadline: .now() + interval) { | |
guarantee.schrödinger = .resolved() | |
} | |
} | |
return guarantee | |
} | |
private protocol _DispatchQoS { | |
var the: DispatchQoS { get } | |
} | |
extension DispatchQoS: _DispatchQoS { | |
var the: DispatchQoS { return self } | |
} | |
extension Optional where Wrapped: _DispatchQoS { | |
@inline(__always) | |
fileprivate func async(execute body: @escaping () -> Void) { | |
switch self { | |
case .none: | |
body() | |
case .some(let qos): | |
DispatchQueue.global().async(group: nil, qos: qos.the, flags: [], execute: body) | |
} | |
} | |
} | |
extension Thenable { | |
public func tap(execute body: @escaping (Result<T>) -> Void) -> Self { | |
pipe(to: body) | |
return self | |
} | |
public var value: T? { | |
switch result { | |
case .fulfilled(let value)?: | |
return value | |
case .rejected?, nil: | |
return nil | |
} | |
} | |
public var isFulfilled: Bool { | |
switch result { | |
case .fulfilled?: | |
return true | |
case .rejected?, nil: | |
return false | |
} | |
} | |
public var isRejected: Bool { | |
switch result { | |
case .rejected?: | |
return true | |
case .fulfilled?, nil: | |
return false | |
} | |
} | |
public var isPending: Bool { | |
switch result { | |
case .fulfilled?, .rejected?: | |
return true | |
case nil: | |
return false | |
} | |
} | |
} | |
public func race<U: Thenable>(_ thenables: U...) -> Promise<U.T> { | |
let result = Promise<U.T>() | |
for thenable in thenables { | |
thenable.pipe{ result.schrödinger = .resolved($0) } | |
} | |
return result | |
} | |
public func when<U, V>(fulfilled u: Promise<U>, _ v: Promise<V>) -> Promise<(U, V)> { | |
return when(fulfilled: [u.asVoid(), v.asVoid()]).then{ _ in (u.value!, v.value!) } | |
} | |
public func when<U, V, X>(fulfilled u: Promise<U>, _ v: Promise<V>, _ x: Promise<X>) -> Promise<(U, V, X)> { | |
return when(fulfilled: [u.asVoid(), v.asVoid(), x.asVoid()]).then{ _ in (u.value!, v.value!, x.value!) } | |
} | |
public func when<U, V, X, Y>(fulfilled u: Promise<U>, _ v: Promise<V>, _ x: Promise<X>, _ y: Promise<Y>) -> Promise<(U, V, X, Y)> { | |
return when(fulfilled: [u.asVoid(), v.asVoid(), x.asVoid(), y.asVoid()]).then{ _ in (u.value!, v.value!, x.value!, y.value!) } | |
} | |
public func when<U, V, X, Y, Z>(fulfilled u: Promise<U>, _ v: Promise<V>, _ x: Promise<X>, _ y: Promise<Y>, _ z: Promise<Z>) -> Promise<(U, V, X, Y, Z)> { | |
return when(fulfilled: [u.asVoid(), v.asVoid(), x.asVoid(), y.asVoid(), z.asVoid()]).then{ _ in (u.value!, v.value!, x.value!, y.value!, z.value!) } | |
} | |
/// - Remark: There is no `...` variant, because it is then confusing that you put a splat in and don't get a splat out, when compared with the typical usage for our above splatted kinds | |
public func when<U: Thenable>(fulfilled thenables: [U]) -> Promise<[U.T]> { | |
let rv = Promise<[U.T]>() | |
var values = Array<U.T!>(repeating: nil, count: thenables.count) | |
var x = thenables.count | |
for (index, thenable) in thenables.enumerated() { | |
thenable.pipe { result in | |
switch result { | |
case .rejected(let error): | |
rv.schrödinger = .resolved(.rejected(error)) | |
case .fulfilled(let value): | |
values[index] = value | |
x -= 1 | |
if x == 0 { | |
rv.schrödinger = .resolved(.fulfilled(values)) | |
} | |
} | |
} | |
} | |
return rv | |
} | |
@discardableResult | |
public func when<U>(fulfilled guarantees: [Guarantee<U>]) -> Guarantee<[U]> { | |
let (rv, seal) = Guarantee<[U]>.pending() | |
var values = Array<U!>(repeating: nil, count: guarantees.count) | |
var x = guarantees.count | |
for (index, guarantee) in guarantees.enumerated() { | |
guarantee.__pipe { value in | |
values[index] = value | |
x -= 1 | |
if x == 0 { | |
seal(values) | |
} | |
} | |
} | |
return rv | |
} | |
extension Promise { | |
func then<U, V>(execute body: @escaping (T) -> (Promise<U>, Promise<V>)) -> Promise<(U,V)> { | |
let promise = Promise<(U, V)>() | |
pipe { result in | |
switch result { | |
case .fulfilled(let value): | |
let (u, v) = body(value) | |
when(fulfilled: u, v).pipe{ promise.schrödinger = .resolved($0) } | |
case .rejected(let error): | |
promise.schrödinger = .resolved(.rejected(error)) | |
} | |
} | |
return promise | |
} | |
func then<U, V, X>(execute body: @escaping (T) -> (Promise<U>, Promise<V>, Promise<X>)) -> Promise<(U,V,X)> { | |
let promise = Promise<(U, V, X)>() | |
pipe { result in | |
switch result { | |
case .fulfilled(let value): | |
let (u, v, x) = body(value) | |
when(fulfilled: u, v, x).pipe{ promise.schrödinger = .resolved($0) } | |
case .rejected(let error): | |
promise.schrödinger = .resolved(.rejected(error)) | |
} | |
} | |
return promise | |
} | |
} | |
public func when<U: Thenable>(resolved thenables: U...) -> Guarantee<[Result<U.T>]> { | |
let (rv, seal) = Guarantee<[Result<U.T>]>.pending() | |
var results = [Result<U.T>]() | |
var x = thenables.count | |
for (index, thenable) in thenables.enumerated() { | |
thenable.pipe { result in | |
results[index] = result | |
x -= 1 | |
if x == 0 { | |
seal(results) | |
} | |
} | |
} | |
return rv | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://gist.github.com/mxcl/c8fcb063d81be0a7a0914047ca509642#file-pmk5-swift-L56
Would 'precondition()' be too heavy-handed? Any such initialization would have to be deliberate, right? Or at least due to gross oversight (maybe even in case it's not our job to crash someone's app unexpectedly though).