Skip to content

Instantly share code, notes, and snippets.

@EricRabil
Created July 30, 2021 02:08
Show Gist options
  • Save EricRabil/7d264bab74f22f8a9748beb9e71f7709 to your computer and use it in GitHub Desktop.
Save EricRabil/7d264bab74f22f8a9748beb9e71f7709 to your computer and use it in GitHub Desktop.
god dammit apple you suk
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