Skip to content

Instantly share code, notes, and snippets.

@mxcl
Created January 21, 2017 19:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mxcl/c8fcb063d81be0a7a0914047ca509642 to your computer and use it in GitHub Desktop.
Save mxcl/c8fcb063d81be0a7a0914047ca509642 to your computer and use it in GitHub Desktop.
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
}
@nathanhosselton
Copy link

nathanhosselton commented Jan 22, 2017

https://gist.github.com/mxcl/c8fcb063d81be0a7a0914047ca509642#file-pmk5-swift-L56

Remark: It is possible to create a Promise<Error> with this method. But please don’t...

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment