Created
July 30, 2021 02:08
-
-
Save EricRabil/7d264bab74f22f8a9748beb9e71f7709 to your computer and use it in GitHub Desktop.
god dammit apple you suk
This file contains hidden or 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 | |
public enum PendingPromise<Output, Failure: Error>: Equatable { | |
public static func == (lhs: PendingPromise<Output, Failure>, rhs: PendingPromise<Output, Failure>) -> Bool { | |
switch lhs { | |
case .pending: | |
switch rhs { | |
case .pending: | |
return true | |
default: | |
return false | |
} | |
case .resolved: | |
switch rhs { | |
case .resolved: | |
return true | |
default: | |
return false | |
} | |
} | |
} | |
case pending | |
case resolved(Result<Output, Failure>) | |
} | |
public protocol PromiseConvertible { | |
associatedtype Output | |
var asPromise: Promise<Output> { get } | |
} | |
public class Promise<Output> { | |
public typealias Pending = PendingPromise<Output, Error> | |
public typealias Completion = Result<Output, Error> | |
public typealias Resolve = (Output) -> () | |
public typealias Reject = (Error) -> () | |
public var resolveLoop = RunLoop.main | |
private var result: Pending = .pending { | |
didSet { | |
emit() | |
} | |
willSet { | |
guard result == .pending, newValue != .pending else { | |
preconditionFailure("result is omnidirectional, from pending to resolved.") | |
} | |
} | |
} | |
private var pending: Bool { | |
result == .pending | |
} | |
private var listeners: [(Completion) -> ()] = [] { | |
didSet { | |
guard listeners.count > 0 else { | |
return | |
} | |
emit() | |
} | |
} | |
private func emit() { | |
guard case .resolved(let result) = result, listeners.count > 0 else { | |
return | |
} | |
let listeners = listeners | |
self.listeners = [] | |
resolveLoop.schedule { | |
listeners.forEach { | |
$0(result) | |
} | |
} | |
} | |
public static func all(_ promises: [Promise<Output>]) -> Promise<[Output]> { | |
let superPromise = Promise<[Output]>() | |
var outputs = [Output]() { | |
didSet { | |
guard superPromise.pending else { | |
outputs = [] | |
return | |
} | |
if outputs.count == promises.count { | |
superPromise.result = .resolved(.success(outputs)) | |
} | |
} | |
} | |
promises.forEach { subPromise in | |
subPromise.always { completion in | |
switch completion { | |
case .success(let output): | |
outputs.append(output) | |
case .failure(let error): | |
superPromise.result = .resolved(.failure(error)) | |
} | |
} | |
} | |
return superPromise | |
} | |
public init(_ cb: (@escaping Resolve, @escaping Reject) -> ()) { | |
cb({ output in | |
self.result = .resolved(.success(output)) | |
}, { error in | |
self.result = .resolved(.failure(error)) | |
}) | |
} | |
public init(_ cb: (@escaping Resolve) -> ()) { | |
cb({ output in | |
self.result = .resolved(.success(output)) | |
}) | |
} | |
public init(result: Completion) { | |
self.result = .resolved(result) | |
} | |
public static func success(_ output: Output) -> Promise<Output> { | |
.completed(.success(output)) | |
} | |
public static func failure(_ error: Error) -> Promise<Output> { | |
.completed(.failure(error)) | |
} | |
public static func completed<Output>(_ result: Promise<Output>.Completion) -> Promise<Output> { | |
Promise<Output>(result: result) | |
} | |
private init() { | |
} | |
@discardableResult | |
public func resolve(on resolveLoop: RunLoop) -> Self { | |
self.resolveLoop = resolveLoop | |
return self | |
} | |
@discardableResult | |
public func always<R>(_ cb: @escaping (Completion) throws -> R) -> Promise<R> { | |
let promise = Promise<R>() | |
listeners.append { result in | |
do { | |
promise.result = .resolved(.success(try cb(result))) | |
} catch { | |
promise.result = .resolved(.failure(error)) | |
} | |
} | |
return promise | |
} | |
@discardableResult | |
public func always<R: PromiseConvertible>(_ cb: @escaping (Completion) throws -> R) -> Promise<R.Output> { | |
let promise = Promise<R.Output>() | |
listeners.append { result in | |
do { | |
try cb(result).asPromise.always { result in | |
promise.result = .resolved(result) | |
} | |
} catch { | |
promise.result = .resolved(.failure(error)) | |
} | |
} | |
return promise | |
} | |
@discardableResult | |
public func then<R: PromiseConvertible>(_ cb: @escaping (Output) throws -> R) -> Promise<R.Output> { | |
let promise = Promise<R.Output>() | |
listeners.append { result in | |
do { | |
switch result { | |
case .success(let output): | |
try cb(output).asPromise.always { result in | |
promise.result = .resolved(result) | |
} | |
case .failure(let error): | |
throw error | |
} | |
} catch { | |
promise.result = .resolved(.failure(error)) | |
} | |
} | |
return promise | |
} | |
@discardableResult | |
public func then<R>(_ cb: @escaping (Output) throws -> R) -> Promise<R> { | |
let promise = Promise<R>() | |
listeners.append { result in | |
do { | |
switch result { | |
case .success(let output): | |
promise.result = .resolved(.success(try cb(output))) | |
case .failure(let error): | |
throw error | |
} | |
} catch { | |
promise.result = .resolved(.failure(error)) | |
} | |
} | |
return promise | |
} | |
@discardableResult | |
public func `catch`<R: PromiseConvertible>(_ cb: @escaping (Error) throws -> R) -> Promise<R.Output> where R.Output == Output { | |
let promise = Promise<Output>() | |
listeners.append { result in | |
do { | |
switch result { | |
case .success: | |
promise.result = .resolved(result) | |
case .failure(let error): | |
try cb(error).asPromise.always { result in | |
promise.result = .resolved(result) | |
} | |
} | |
} catch { | |
promise.result = .resolved(.failure(error)) | |
} | |
} | |
return promise | |
} | |
@discardableResult | |
public func `catch`(_ cb: @escaping (Error) throws -> Output) -> Promise<Output> { | |
let promise = Promise<Output>() | |
listeners.append { result in | |
do { | |
switch result { | |
case .success: | |
promise.result = .resolved(result) | |
case .failure(let error): | |
promise.result = .resolved(.success(try cb(error))) | |
} | |
} catch { | |
promise.result = .resolved(.failure(error)) | |
} | |
} | |
return promise | |
} | |
} | |
public protocol OptionalConvertible { | |
associatedtype Element | |
var asOptional: Optional<Element> { get } | |
} | |
extension Optional: OptionalConvertible { | |
public var asOptional: Optional<Wrapped> { return self } | |
} | |
extension Promise: PromiseConvertible { | |
public var asPromise: Promise<Output> { | |
self | |
} | |
} | |
extension Promise where Output: OptionalConvertible { | |
public func assert(_ error: @autoclosure @escaping () -> Error) -> Promise<Output.Element> { | |
then { output in | |
guard let output = output.asOptional else { | |
throw error() | |
} | |
return output | |
} | |
} | |
} | |
public extension DispatchQueue { | |
func promise<Output>(_ cb: @escaping () throws -> Output) -> Promise<Output> { | |
Promise { resolve, reject in | |
self.schedule { | |
do { | |
resolve(try cb()) | |
} catch { | |
reject(error) | |
} | |
} | |
} | |
} | |
func promise<R: PromiseConvertible>(_ cb: @escaping () throws -> R) -> Promise<R.Output> { | |
Promise { resolve, reject in | |
self.schedule { | |
do { | |
try cb().asPromise.then(resolve).catch(reject) | |
} catch { | |
reject(error) | |
} | |
} | |
} | |
} | |
} | |
public extension Promise { | |
func observe(_ cb: @escaping (Output) -> Void) -> Promise<Output> { | |
then { output in | |
cb(output) | |
return output | |
} | |
} | |
func observeAlways(_ cb: @escaping (Completion) -> Void) -> Promise<Output> { | |
always { result -> Promise<Output> in | |
cb(result) | |
return Promise.completed(result) | |
} | |
} | |
} | |
public extension NotificationCenter { | |
func promise(for notificationName: Notification.Name, object: Any? = nil, queue: OperationQueue? = nil) -> Promise<Notification> { | |
var observer: NSObjectProtocol! | |
return Promise { resolve in | |
observer = self.addObserver(forName: notificationName, object: object, queue: queue) { notification in | |
self.removeObserver(observer!) | |
observer = nil | |
resolve(notification) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment